415 lines
14 KiB
C++
415 lines
14 KiB
C++
#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 <algorithm>
|
|
#include <cmath>
|
|
#include <cstdint>
|
|
#include <vector>
|
|
|
|
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::uint32_t>(std::max(width, 0)),
|
|
.height = static_cast<std::uint32_t>(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<void()> save_state;
|
|
std::function<void(const LegacyNodeStrokePreviewMixPassPlan::ShaderPlan&)> setup_mix_shader;
|
|
std::function<void()> bind_mixer_framebuffer;
|
|
std::function<void(int, int, int, int, int, int)> configure_mix_target_state;
|
|
std::function<void()> bind_mix_inputs;
|
|
std::function<void()> draw_mix;
|
|
std::function<void()> unbind_mixer_framebuffer;
|
|
std::function<void()> 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<float>(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<void()> prepare_dual_pass;
|
|
std::function<void()> execute_dual_pass;
|
|
std::function<void()> capture_background;
|
|
std::function<void()> prepare_main_pass;
|
|
std::function<void()> execute_main_pass;
|
|
std::function<void()> finish_main_pass;
|
|
std::function<void()> execute_final_composite;
|
|
std::function<void()> 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<int>(request.preview_size.x),
|
|
static_cast<int>(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<float>(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<LegacyNodeStrokePreviewStrokePoint> 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<glm::vec2> 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<glm::vec2> 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<std::size_t>(point_count));
|
|
for (int i = 0; i < point_count; ++i) {
|
|
const float t = static_cast<float>(i) / static_cast<float>(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
|