Files
panopainter/src/legacy_canvas_stroke_execution_services.h

1080 lines
38 KiB
C++

#pragma once
#include "paint_renderer/compositor.h"
#include "brush.h"
#include "../libs/glm/glm/glm.hpp"
#include "../libs/glm/glm/gtc/matrix_transform.hpp"
#include "../libs/glm/glm/gtx/rotate_vector.hpp"
#include "util.h"
#include <array>
#include <cstddef>
#include <functional>
#include <span>
#include <string_view>
#include <utility>
#include <vector>
namespace pp::panopainter {
struct LegacyStrokeSampleExecutionRequest {
std::string_view context;
glm::vec2 target_size {};
std::span<const vertex_t> vertices;
std::span<const pp::paint_renderer::CanvasStrokePoint> sample_points;
bool copy_stroke_destination = false;
std::function<void()> bind_destination_texture;
std::function<void(int, int, int, int, int, int)> copy_framebuffer_to_destination_texture;
std::function<void()> unbind_destination_texture;
std::function<void(std::span<const vertex_t>)> upload_brush_vertices;
std::function<void()> draw_brush_shape;
};
struct LegacyStrokeSampleExecutionResult {
bool ok = false;
glm::ivec2 copy_position {};
glm::ivec2 copy_size {};
glm::vec4 dirty_bounds {};
};
struct LegacyStrokeSamplePolygonExecutionRequest {
std::string_view context;
glm::vec2 target_size {};
std::span<const vertex_t> polygon_vertices;
bool copy_stroke_destination = false;
std::function<void()> bind_destination_texture;
std::function<void(int, int, int, int, int, int)> copy_framebuffer_to_destination_texture;
std::function<void()> unbind_destination_texture;
std::function<void(std::span<const vertex_t>)> upload_brush_vertices;
std::function<void()> draw_brush_shape;
};
struct LegacyStrokeFaceSamplePolygonExecutionRequest {
std::string_view context;
glm::vec2 target_size {};
std::span<const vertex_t> polygon_vertices;
int face_index = 0;
bool copy_stroke_destination = false;
std::function<void(int)> bind_destination_texture;
std::function<void(int, int, int, int, int, int, int)> copy_framebuffer_to_destination_texture;
std::function<void(int)> unbind_destination_texture;
std::function<void(int, std::span<const vertex_t>)> upload_brush_vertices;
std::function<void(int)> draw_brush_shape;
};
enum class LegacyCanvasStrokeTextureInput {
brush_tip,
stroke_destination,
pattern,
mixer,
};
struct LegacyCanvasStrokeTextureBinding {
LegacyCanvasStrokeTextureInput input = LegacyCanvasStrokeTextureInput::brush_tip;
int slot = 0;
};
struct LegacyCanvasStrokeTextureInputDispatch {
std::function<void(int)> activate_texture_unit;
std::function<void()> bind_brush_tip;
std::function<void()> unbind_brush_tip;
std::function<void()> bind_stroke_destination;
std::function<void()> unbind_stroke_destination;
std::function<void()> bind_pattern;
std::function<void()> unbind_pattern;
std::function<void()> bind_mixer;
std::function<void()> unbind_mixer;
};
struct LegacyCanvasStrokeSamplerDispatch {
std::function<void(int)> bind_brush_tip_sampler;
std::function<void()> unbind_brush_tip_sampler;
std::function<void(int)> bind_stroke_destination_sampler;
std::function<void()> unbind_stroke_destination_sampler;
std::function<void(int)> bind_pattern_sampler;
std::function<void()> unbind_pattern_sampler;
std::function<void(int)> bind_mixer_sampler;
std::function<void()> unbind_mixer_sampler;
};
struct LegacyCanvasStrokeFaceDirtyRequest {
pp::renderer::Extent2D extent {};
glm::vec4 previous_accumulated_dirty_box {};
glm::vec4 previous_pass_dirty_box {};
glm::vec4 sample_dirty_box {};
bool include_in_committed_dirty_box = true;
};
struct LegacyCanvasStrokeFaceDirtyResult {
glm::vec4 accumulated_dirty_box {};
glm::vec4 pass_dirty_box {};
bool has_dirty_pixels = false;
bool committed_dirty = false;
bool pass_dirty = false;
};
struct LegacyCanvasStrokePadRegionRequest {
pp::renderer::Extent2D extent {};
glm::vec4 pass_dirty_box {};
};
struct LegacyCanvasStrokePadRegionResult {
bool has_pixels = false;
pp::paint_renderer::CanvasStrokeCopyRegion copy_region {};
std::array<pp::paint_renderer::CanvasStrokePoint, 6> ndc_quad {};
};
struct LegacyCanvasStrokePadFace {
int index = 0;
bool dirty = false;
glm::vec4 pass_dirty_box {};
};
struct LegacyCanvasStrokePadExecutionRequest {
std::string_view context;
pp::renderer::Extent2D extent {};
std::span<const LegacyCanvasStrokePadFace> faces;
bool copy_stroke_destination = false;
std::function<void(std::span<const vertex_t>)> upload_pad_vertices;
std::function<void(int)> begin_face;
std::function<void(int)> bind_destination_texture;
std::function<void(const pp::paint_renderer::CanvasStrokeCopyRegion&)> copy_framebuffer_to_destination_texture;
std::function<void(int)> unbind_destination_texture;
std::function<void()> draw_pad;
std::function<void(int)> finish_face;
std::function<void(int, const LegacyCanvasStrokePadRegionResult&, std::span<vertex_t>)> execute_face;
};
struct LegacyCanvasStrokePadExecutionResult {
bool ok = false;
std::size_t padded_faces = 0;
};
struct LegacyCanvasStrokeMixPassPlane {
int index = 0;
bool visible = true;
bool has_target = false;
float opacity = 1.0f;
glm::mat4 mvp { 1.0f };
};
struct LegacyCanvasStrokeMixPassRequest {
std::string_view context;
glm::vec2 resolution {};
std::span<const LegacyCanvasStrokeMixPassPlane> planes;
std::function<void()> bind_mix_samplers;
std::function<void()> unbind_mix_samplers;
std::function<void(int, const glm::mat4&)> setup_plane_shader;
std::function<void(int)> bind_layer_texture;
std::function<void(int)> bind_stroke_texture;
std::function<void(int)> bind_mask_texture;
std::function<void()> draw_plane;
std::function<void(int)> unbind_mask_texture;
std::function<void(int)> unbind_stroke_texture;
std::function<void(int)> unbind_layer_texture;
};
struct LegacyCanvasStrokeMixPassResult {
bool ok = false;
std::size_t composed_planes = 0;
};
struct LegacyCanvasStrokeComputeRequest {
StrokeSample previous_sample {};
std::span<const StrokeSample> samples;
float zoom = 1.0f;
glm::vec2 mixer_size {};
glm::mat4 model_view { 1.0f };
};
template <typename ProjectStroke, typename MakeFrame>
[[nodiscard]] auto plan_legacy_canvas_stroke_frames(
const LegacyCanvasStrokeComputeRequest& request,
ProjectStroke&& project_stroke,
MakeFrame&& make_frame)
{
using StrokeFrame = decltype(make_frame(
std::declval<glm::vec4>(),
std::declval<glm::vec4>(),
0.0f,
0.0f,
project_stroke(std::declval<std::array<vertex_t, 4>&>(), false, request.model_view)));
std::vector<StrokeFrame> frames;
StrokeSample prev = request.previous_sample;
std::array<vertex_t, 4> brush_quad = {
vertex_t{ {0, 0, 1, 1}, {0, 0}, {0, 0} },
vertex_t{ {0, 0, 1, 1}, {0, 1}, {0, 1} },
vertex_t{ {0, 0, 1, 1}, {1, 1}, {1, 1} },
vertex_t{ {0, 0, 1, 1}, {1, 0}, {1, 0} },
};
for (const auto& sample : request.samples) {
if (!sample.valid()) {
continue;
}
const glm::vec2 mixer_dx(prev.size * 0.5f, 0);
const glm::vec2 mixer_dy(0, prev.size * 0.5f);
const glm::vec2 mixer_offsets[4] = {
-mixer_dx - mixer_dy,
-mixer_dx + mixer_dy,
+mixer_dx + mixer_dy,
+mixer_dx - mixer_dy,
};
const glm::vec2 brush_dx(sample.size * 0.5f, 0);
const glm::vec2 brush_dy(0, sample.size * 0.5f);
const glm::vec2 brush_offsets[4] = {
-brush_dx - brush_dy,
-brush_dx + brush_dy,
+brush_dx + brush_dy,
+brush_dx - brush_dy,
};
glm::vec2 mixer_bb_min(request.mixer_size);
glm::vec2 mixer_bb_max(0, 0);
for (int i = 0; i < 4; ++i) {
const auto mixer_point =
xy(prev.pos) + request.zoom * sample.scale * mixer_offsets[i] * glm::orientate2(-sample.angle);
mixer_bb_min = glm::max(glm::vec2(0, 0), glm::min(mixer_bb_min, mixer_point));
mixer_bb_max = glm::min(request.mixer_size, glm::max(mixer_bb_max, mixer_point));
if (sample.pos.z == 0.0f) {
brush_quad[i].pos = glm::vec4(
xy(sample.pos) +
request.zoom * sample.scale * brush_offsets[i] * glm::orientate2(-sample.angle) -
glm::vec2(0, 1),
1,
1);
} else {
const auto inverse_view = glm::inverse(glm::lookAt({ 0, 0, 0 }, sample.pos, { 0, 1, 0 }));
const glm::vec3 offset_3d = inverse_view * glm::vec4(brush_offsets[i], 0, 1);
brush_quad[i].pos = glm::vec4(sample.pos + offset_3d, 1.0f);
}
brush_quad[i].uvs2 = mixer_point / request.mixer_size;
}
const auto mixer_rect =
glm::vec4(glm::floor(mixer_bb_min), glm::ceil(mixer_bb_max - mixer_bb_min)) / request.zoom;
auto shapes = sample.pos.z == 0.0f
? project_stroke(brush_quad, false, request.model_view)
: project_stroke(brush_quad, true, glm::lookAt({ 0, 0, 0 }, sample.pos, { 0, 1, 0 }));
frames.push_back(make_frame(mixer_rect, glm::vec4(sample.col, 1), sample.flow, sample.opacity, std::move(shapes)));
prev = sample;
}
return frames;
}
template <typename Frames, typename BeginFrame, typename ExecuteFace>
std::size_t execute_legacy_canvas_stroke_frame_faces(
Frames&& frames,
BeginFrame&& begin_frame,
ExecuteFace&& execute_face)
{
std::size_t executed_faces = 0;
for (auto& frame : frames) {
begin_frame(frame);
for (int face_index = 0; face_index < 6; ++face_index) {
auto& vertices = frame.shapes[face_index];
if (vertices.size() < 3) {
continue;
}
execute_face(frame, face_index, vertices);
++executed_faces;
}
}
return executed_faces;
}
template <typename BindTextureInput, std::size_t BindingCount>
requires std::invocable<BindTextureInput&, LegacyCanvasStrokeTextureInput, int>
inline void bind_legacy_canvas_stroke_texture_inputs(
const std::array<LegacyCanvasStrokeTextureBinding, BindingCount>& bindings,
BindTextureInput&& bind_texture_input)
{
for (const auto& binding : bindings) {
bind_texture_input(binding.input, binding.slot);
}
}
template <typename UnbindTextureInput, std::size_t BindingCount>
requires std::invocable<UnbindTextureInput&, LegacyCanvasStrokeTextureInput, int>
inline void unbind_legacy_canvas_stroke_texture_inputs(
const std::array<LegacyCanvasStrokeTextureBinding, BindingCount>& bindings,
UnbindTextureInput&& unbind_texture_input)
{
for (const auto& binding : bindings) {
unbind_texture_input(binding.input, binding.slot);
}
}
inline void bind_legacy_canvas_stroke_texture_input(
LegacyCanvasStrokeTextureInput input,
const LegacyCanvasStrokeTextureInputDispatch& dispatch)
{
switch (input) {
case LegacyCanvasStrokeTextureInput::brush_tip:
if (dispatch.bind_brush_tip) {
dispatch.bind_brush_tip();
}
break;
case LegacyCanvasStrokeTextureInput::stroke_destination:
if (dispatch.bind_stroke_destination) {
dispatch.bind_stroke_destination();
}
break;
case LegacyCanvasStrokeTextureInput::pattern:
if (dispatch.bind_pattern) {
dispatch.bind_pattern();
}
break;
case LegacyCanvasStrokeTextureInput::mixer:
if (dispatch.bind_mixer) {
dispatch.bind_mixer();
}
break;
}
}
inline void unbind_legacy_canvas_stroke_texture_input(
LegacyCanvasStrokeTextureInput input,
const LegacyCanvasStrokeTextureInputDispatch& dispatch)
{
switch (input) {
case LegacyCanvasStrokeTextureInput::brush_tip:
if (dispatch.unbind_brush_tip) {
dispatch.unbind_brush_tip();
}
break;
case LegacyCanvasStrokeTextureInput::stroke_destination:
if (dispatch.unbind_stroke_destination) {
dispatch.unbind_stroke_destination();
}
break;
case LegacyCanvasStrokeTextureInput::pattern:
if (dispatch.unbind_pattern) {
dispatch.unbind_pattern();
}
break;
case LegacyCanvasStrokeTextureInput::mixer:
if (dispatch.unbind_mixer) {
dispatch.unbind_mixer();
}
break;
}
}
inline void bind_legacy_canvas_stroke_sampler_input(
LegacyCanvasStrokeTextureInput input,
int slot,
const LegacyCanvasStrokeSamplerDispatch& dispatch)
{
switch (input) {
case LegacyCanvasStrokeTextureInput::brush_tip:
if (dispatch.bind_brush_tip_sampler) {
dispatch.bind_brush_tip_sampler(slot);
}
break;
case LegacyCanvasStrokeTextureInput::stroke_destination:
if (dispatch.bind_stroke_destination_sampler) {
dispatch.bind_stroke_destination_sampler(slot);
}
break;
case LegacyCanvasStrokeTextureInput::pattern:
if (dispatch.bind_pattern_sampler) {
dispatch.bind_pattern_sampler(slot);
}
break;
case LegacyCanvasStrokeTextureInput::mixer:
if (dispatch.bind_mixer_sampler) {
dispatch.bind_mixer_sampler(slot);
}
break;
}
}
inline void unbind_legacy_canvas_stroke_sampler_input(
LegacyCanvasStrokeTextureInput input,
const LegacyCanvasStrokeSamplerDispatch& dispatch)
{
switch (input) {
case LegacyCanvasStrokeTextureInput::brush_tip:
if (dispatch.unbind_brush_tip_sampler) {
dispatch.unbind_brush_tip_sampler();
}
break;
case LegacyCanvasStrokeTextureInput::stroke_destination:
if (dispatch.unbind_stroke_destination_sampler) {
dispatch.unbind_stroke_destination_sampler();
}
break;
case LegacyCanvasStrokeTextureInput::pattern:
if (dispatch.unbind_pattern_sampler) {
dispatch.unbind_pattern_sampler();
}
break;
case LegacyCanvasStrokeTextureInput::mixer:
if (dispatch.unbind_mixer_sampler) {
dispatch.unbind_mixer_sampler();
}
break;
}
}
template <std::size_t BindingCount>
inline void bind_legacy_canvas_stroke_texture_inputs(
const std::array<LegacyCanvasStrokeTextureBinding, BindingCount>& bindings,
const LegacyCanvasStrokeTextureInputDispatch& dispatch)
{
for (const auto& binding : bindings) {
if (dispatch.activate_texture_unit) {
dispatch.activate_texture_unit(binding.slot);
}
bind_legacy_canvas_stroke_texture_input(binding.input, dispatch);
}
}
template <std::size_t BindingCount>
inline void unbind_legacy_canvas_stroke_texture_inputs(
const std::array<LegacyCanvasStrokeTextureBinding, BindingCount>& bindings,
const LegacyCanvasStrokeTextureInputDispatch& dispatch)
{
for (const auto& binding : bindings) {
if (dispatch.activate_texture_unit) {
dispatch.activate_texture_unit(binding.slot);
}
unbind_legacy_canvas_stroke_texture_input(binding.input, dispatch);
}
}
template <std::size_t BindingCount>
inline void bind_legacy_canvas_stroke_sampler_inputs(
const std::array<LegacyCanvasStrokeTextureBinding, BindingCount>& bindings,
const LegacyCanvasStrokeSamplerDispatch& dispatch)
{
for (const auto& binding : bindings) {
bind_legacy_canvas_stroke_sampler_input(binding.input, binding.slot, dispatch);
}
}
template <std::size_t BindingCount>
inline void unbind_legacy_canvas_stroke_sampler_inputs(
const std::array<LegacyCanvasStrokeTextureBinding, BindingCount>& bindings,
const LegacyCanvasStrokeSamplerDispatch& dispatch)
{
for (const auto& binding : bindings) {
unbind_legacy_canvas_stroke_sampler_input(binding.input, dispatch);
}
}
template <typename Frames, typename BeginFrame, typename BeginFace, typename ExecuteSample, typename FinishFace>
std::size_t execute_legacy_canvas_stroke_frame_samples(
Frames&& frames,
BeginFrame&& begin_frame,
BeginFace&& begin_face,
ExecuteSample&& execute_sample,
FinishFace&& finish_face)
{
std::size_t executed_faces = 0;
for (auto& frame : frames) {
begin_frame(frame);
for (int face_index = 0; face_index < 6; ++face_index) {
auto& vertices = frame.shapes[face_index];
if (vertices.size() < 3) {
continue;
}
begin_face(frame, face_index, vertices);
auto sample_dirty_box = execute_sample(frame, face_index, vertices);
finish_face(frame, face_index, sample_dirty_box);
++executed_faces;
}
}
return executed_faces;
}
template <
typename Frames,
typename BeginFrame,
typename BeginFace,
typename ExecuteSample,
typename PrepareDirtyRequest,
typename FinishFace>
std::size_t execute_legacy_canvas_stroke_frame_samples_with_dirty_tracking(
Frames&& frames,
pp::renderer::Extent2D extent,
std::span<glm::vec4> accumulated_dirty_boxes,
std::span<glm::vec4> pass_dirty_boxes,
std::span<const bool> include_in_committed_dirty_box,
BeginFrame&& begin_frame,
BeginFace&& begin_face,
ExecuteSample&& execute_sample,
PrepareDirtyRequest&& prepare_dirty_request,
FinishFace&& finish_face,
std::span<bool> committed_dirty_faces = {},
std::span<bool> pass_dirty_faces = {})
{
std::size_t executed_faces = 0;
for (auto& frame : frames) {
begin_frame(frame);
for (int face_index = 0; face_index < 6; ++face_index) {
auto& vertices = frame.shapes[face_index];
if (vertices.size() < 3) {
continue;
}
const glm::vec4 previous_accumulated_dirty_box =
face_index < accumulated_dirty_boxes.size()
? accumulated_dirty_boxes[face_index]
: glm::vec4(0.0f);
const glm::vec4 previous_pass_dirty_box =
face_index < pass_dirty_boxes.size()
? pass_dirty_boxes[face_index]
: glm::vec4(0.0f);
const bool include_dirty_box =
face_index < include_in_committed_dirty_box.size()
? include_in_committed_dirty_box[face_index]
: true;
glm::vec4* accumulated_dirty_box =
face_index < accumulated_dirty_boxes.size()
? &accumulated_dirty_boxes[face_index]
: nullptr;
glm::vec4* pass_dirty_box =
face_index < pass_dirty_boxes.size()
? &pass_dirty_boxes[face_index]
: nullptr;
bool* committed_dirty =
face_index < committed_dirty_faces.size()
? &committed_dirty_faces[face_index]
: nullptr;
bool* pass_dirty =
face_index < pass_dirty_faces.size()
? &pass_dirty_faces[face_index]
: nullptr;
execute_legacy_canvas_stroke_face_sample(
LegacyCanvasStrokeFaceDirtyRequest {
.extent = extent,
.previous_accumulated_dirty_box = previous_accumulated_dirty_box,
.previous_pass_dirty_box = previous_pass_dirty_box,
.include_in_committed_dirty_box = include_dirty_box,
},
[&] {
return execute_sample(frame, face_index, vertices);
},
[&] {
begin_face(frame, face_index, vertices);
},
[&](auto& request) {
prepare_dirty_request(frame, face_index, vertices, request);
},
[&](glm::vec4 sample_dirty_box) {
finish_face(frame, face_index, vertices, sample_dirty_box);
},
accumulated_dirty_box,
pass_dirty_box,
committed_dirty,
pass_dirty);
++executed_faces;
}
}
return executed_faces;
}
template <typename Frames, typename BeginFrame, typename BeginFace, typename ExecuteSample, typename FinishFace>
std::size_t execute_legacy_canvas_stroke_live_pass_with_dirty_tracking(
Frames&& frames,
pp::renderer::Extent2D extent,
std::span<glm::vec4> accumulated_dirty_boxes,
std::span<glm::vec4> pass_dirty_boxes,
std::span<const bool> include_in_committed_dirty_box,
BeginFrame&& begin_frame,
BeginFace&& begin_face,
ExecuteSample&& execute_sample,
FinishFace&& finish_face,
bool preserve_sample_dirty_as_pass_dirty = false,
std::span<bool> committed_dirty_faces = {},
std::span<bool> pass_dirty_faces = {})
{
return execute_legacy_canvas_stroke_frame_samples_with_dirty_tracking(
std::forward<Frames>(frames),
extent,
accumulated_dirty_boxes,
pass_dirty_boxes,
include_in_committed_dirty_box,
std::forward<BeginFrame>(begin_frame),
std::forward<BeginFace>(begin_face),
std::forward<ExecuteSample>(execute_sample),
[&](auto&, int, auto&, auto& request) {
if (preserve_sample_dirty_as_pass_dirty) {
request.previous_pass_dirty_box = request.sample_dirty_box;
}
},
[&](auto& frame, int face_index, auto& vertices, glm::vec4 sample_dirty_box) {
finish_face(frame, face_index, vertices, sample_dirty_box);
},
committed_dirty_faces,
pass_dirty_faces);
}
template <typename Frames, typename Framebuffers, typename BeginFrame, typename PrepareFace, typename ExecuteSample>
std::size_t execute_legacy_canvas_stroke_live_pass_with_face_framebuffers(
Frames&& frames,
pp::renderer::Extent2D extent,
std::span<glm::vec4> accumulated_dirty_boxes,
std::span<glm::vec4> pass_dirty_boxes,
std::span<const bool> include_in_committed_dirty_box,
BeginFrame&& begin_frame,
PrepareFace&& prepare_face,
ExecuteSample&& execute_sample,
Framebuffers& face_framebuffers,
bool preserve_sample_dirty_as_pass_dirty = false,
std::span<bool> committed_dirty_faces = {},
std::span<bool> pass_dirty_faces = {})
{
return execute_legacy_canvas_stroke_live_pass_with_dirty_tracking(
std::forward<Frames>(frames),
extent,
accumulated_dirty_boxes,
pass_dirty_boxes,
include_in_committed_dirty_box,
std::forward<BeginFrame>(begin_frame),
[&](auto& frame, int face_index, auto& vertices) {
prepare_face(frame, face_index, vertices);
face_framebuffers[face_index].bindFramebuffer();
},
std::forward<ExecuteSample>(execute_sample),
[&](auto&, int face_index, auto&, glm::vec4) {
face_framebuffers[face_index].unbindFramebuffer();
},
preserve_sample_dirty_as_pass_dirty,
committed_dirty_faces,
pass_dirty_faces);
}
template <
typename SetupCompositeShader,
typename BindCompositeSamplers,
typename BindCompositeTextures,
typename DrawComposite,
typename UnbindCompositeTextures>
void execute_legacy_canvas_stroke_temporary_composite(
SetupCompositeShader&& setup_composite_shader,
BindCompositeSamplers&& bind_composite_samplers,
BindCompositeTextures&& bind_composite_textures,
DrawComposite&& draw_composite,
UnbindCompositeTextures&& unbind_composite_textures)
{
setup_composite_shader();
bind_composite_samplers();
bind_composite_textures();
draw_composite();
unbind_composite_textures();
}
[[nodiscard]] inline pp::paint_renderer::CanvasStrokeBox legacy_canvas_stroke_box(glm::vec4 box) noexcept
{
return pp::paint_renderer::CanvasStrokeBox {
.min_x = box.x,
.min_y = box.y,
.max_x = box.z,
.max_y = box.w,
};
}
[[nodiscard]] inline glm::vec4 legacy_canvas_stroke_glm_box(
pp::paint_renderer::CanvasStrokeBox box) noexcept
{
return glm::vec4(box.min_x, box.min_y, box.max_x, box.max_y);
}
[[nodiscard]] inline LegacyCanvasStrokePadRegionResult plan_legacy_canvas_stroke_pad_region(
const LegacyCanvasStrokePadRegionRequest& request) noexcept
{
const auto plan = pp::paint_renderer::plan_canvas_stroke_pad_region(
pp::paint_renderer::CanvasStrokePadRegionRequest {
.extent = request.extent,
.pass_dirty_box = legacy_canvas_stroke_box(request.pass_dirty_box),
});
return LegacyCanvasStrokePadRegionResult {
.has_pixels = plan.has_pixels,
.copy_region = plan.copy_region,
.ndc_quad = plan.ndc_quad,
};
}
[[nodiscard]] inline LegacyCanvasStrokeFaceDirtyResult plan_legacy_canvas_stroke_face_dirty_update(
const LegacyCanvasStrokeFaceDirtyRequest& request) noexcept
{
const auto plan = pp::paint_renderer::plan_canvas_stroke_face_dirty_update(
pp::paint_renderer::CanvasStrokeFaceDirtyUpdateRequest {
.extent = request.extent,
.previous_accumulated_dirty_box =
legacy_canvas_stroke_box(request.previous_accumulated_dirty_box),
.previous_pass_dirty_box = legacy_canvas_stroke_box(request.previous_pass_dirty_box),
.sample_dirty_box = legacy_canvas_stroke_box(request.sample_dirty_box),
.include_in_committed_dirty_box = request.include_in_committed_dirty_box,
});
return LegacyCanvasStrokeFaceDirtyResult {
.accumulated_dirty_box = legacy_canvas_stroke_glm_box(plan.accumulated_dirty_box),
.pass_dirty_box = legacy_canvas_stroke_glm_box(plan.pass_dirty_box),
.has_dirty_pixels = plan.has_dirty_pixels,
.committed_dirty = plan.committed_dirty,
.pass_dirty = plan.pass_dirty,
};
}
template <std::size_t FaceCount>
[[nodiscard]] inline std::array<LegacyCanvasStrokePadFace, FaceCount> make_legacy_canvas_stroke_pad_faces(
const std::array<bool, FaceCount>& dirty_faces,
const std::array<glm::vec4, FaceCount>& pass_dirty_boxes) noexcept
{
std::array<LegacyCanvasStrokePadFace, FaceCount> faces {};
for (std::size_t face_index = 0; face_index < FaceCount; ++face_index) {
faces[face_index].index = static_cast<int>(face_index);
faces[face_index].dirty = dirty_faces[face_index];
faces[face_index].pass_dirty_box = pass_dirty_boxes[face_index];
}
return faces;
}
[[nodiscard]] inline LegacyCanvasStrokeFaceDirtyResult apply_legacy_canvas_stroke_face_dirty_update(
const LegacyCanvasStrokeFaceDirtyRequest& request,
glm::vec4& accumulated_dirty_box,
glm::vec4* pass_dirty_box = nullptr,
bool* committed_dirty = nullptr,
bool* pass_dirty = nullptr) noexcept
{
const auto result = plan_legacy_canvas_stroke_face_dirty_update(request);
accumulated_dirty_box = result.accumulated_dirty_box;
if (pass_dirty_box) {
*pass_dirty_box = result.pass_dirty_box;
}
if (committed_dirty) {
*committed_dirty = result.committed_dirty;
}
if (pass_dirty) {
*pass_dirty = result.pass_dirty;
}
return result;
}
template <typename ExecuteSample, typename BeginFace, typename PrepareDirtyRequest, typename FinishFace>
[[nodiscard]] inline LegacyCanvasStrokeFaceDirtyResult execute_legacy_canvas_stroke_face_sample(
const LegacyCanvasStrokeFaceDirtyRequest& dirty_request,
ExecuteSample&& execute_sample,
BeginFace&& begin_face,
PrepareDirtyRequest&& prepare_dirty_request,
FinishFace&& finish_face,
glm::vec4* accumulated_dirty_box = nullptr,
glm::vec4* pass_dirty_box = nullptr,
bool* committed_dirty = nullptr,
bool* pass_dirty = nullptr) noexcept(noexcept(execute_sample()))
{
LegacyCanvasStrokeFaceDirtyResult result;
begin_face();
auto request = dirty_request;
request.sample_dirty_box = execute_sample();
prepare_dirty_request(request);
finish_face(request.sample_dirty_box);
glm::vec4 accumulated = request.previous_accumulated_dirty_box;
result = apply_legacy_canvas_stroke_face_dirty_update(
request,
accumulated,
pass_dirty_box,
committed_dirty,
pass_dirty);
result.accumulated_dirty_box = accumulated;
if (accumulated_dirty_box) {
*accumulated_dirty_box = accumulated;
}
return result;
}
[[nodiscard]] inline LegacyCanvasStrokePadExecutionResult execute_legacy_canvas_stroke_pad_faces(
const LegacyCanvasStrokePadExecutionRequest& request)
{
LegacyCanvasStrokePadExecutionResult result;
if (request.extent.width == 0U ||
request.extent.height == 0U ||
(!request.execute_face &&
(!request.upload_pad_vertices ||
!request.begin_face ||
!request.draw_pad ||
!request.finish_face))) {
return result;
}
for (const auto& face : request.faces) {
if (!face.dirty) {
continue;
}
const auto pad_region = plan_legacy_canvas_stroke_pad_region(
LegacyCanvasStrokePadRegionRequest {
.extent = request.extent,
.pass_dirty_box = face.pass_dirty_box,
});
if (!pad_region.has_pixels) {
continue;
}
std::array<vertex_t, 6> pad_quad = {
vertex_t({ pad_region.ndc_quad[0].x, pad_region.ndc_quad[0].y }),
vertex_t({ pad_region.ndc_quad[1].x, pad_region.ndc_quad[1].y }),
vertex_t({ pad_region.ndc_quad[2].x, pad_region.ndc_quad[2].y }),
vertex_t({ pad_region.ndc_quad[3].x, pad_region.ndc_quad[3].y }),
vertex_t({ pad_region.ndc_quad[4].x, pad_region.ndc_quad[4].y }),
vertex_t({ pad_region.ndc_quad[5].x, pad_region.ndc_quad[5].y }),
};
if (request.execute_face) {
request.execute_face(face.index, pad_region, pad_quad);
} else {
if (!request.upload_pad_vertices ||
!request.begin_face ||
!request.draw_pad ||
!request.finish_face) {
return result;
}
if (request.copy_stroke_destination &&
(!request.bind_destination_texture ||
!request.copy_framebuffer_to_destination_texture ||
!request.unbind_destination_texture)) {
return result;
}
request.upload_pad_vertices(pad_quad);
request.begin_face(face.index);
if (request.copy_stroke_destination) {
request.bind_destination_texture(face.index);
request.copy_framebuffer_to_destination_texture(pad_region.copy_region);
}
request.draw_pad();
if (request.copy_stroke_destination) {
request.unbind_destination_texture(face.index);
}
request.finish_face(face.index);
}
++result.padded_faces;
}
result.ok = true;
return result;
}
[[nodiscard]] inline LegacyStrokeSampleExecutionResult execute_legacy_canvas_stroke_sample(
const LegacyStrokeSampleExecutionRequest& request)
{
LegacyStrokeSampleExecutionResult result;
if (request.target_size.x <= 0.0f ||
request.target_size.y <= 0.0f ||
request.vertices.empty() ||
!request.upload_brush_vertices ||
!request.draw_brush_shape) {
return result;
}
if (request.copy_stroke_destination) {
if (!request.bind_destination_texture ||
!request.copy_framebuffer_to_destination_texture ||
!request.unbind_destination_texture) {
return result;
}
request.bind_destination_texture();
}
const auto sample_bounds = pp::paint_renderer::plan_canvas_stroke_sample_bounds(
pp::paint_renderer::CanvasStrokeSampleBoundsRequest {
.extent = pp::renderer::Extent2D {
.width = static_cast<std::uint32_t>(request.target_size.x),
.height = static_cast<std::uint32_t>(request.target_size.y),
},
.vertices = request.sample_points,
});
if (!sample_bounds.has_pixels) {
if (request.copy_stroke_destination) {
request.unbind_destination_texture();
}
return result;
}
result.copy_position = glm::ivec2(sample_bounds.copy_region.x, sample_bounds.copy_region.y);
result.copy_size = glm::ivec2(sample_bounds.copy_region.width, sample_bounds.copy_region.height);
result.dirty_bounds = glm::vec4(
sample_bounds.dirty_bounds.min_x,
sample_bounds.dirty_bounds.min_y,
sample_bounds.dirty_bounds.max_x,
sample_bounds.dirty_bounds.max_y);
if (request.copy_stroke_destination) {
request.copy_framebuffer_to_destination_texture(
result.copy_position.x,
result.copy_position.y,
result.copy_position.x,
result.copy_position.y,
result.copy_size.x,
result.copy_size.y);
}
if (request.vertices.size() == 4) {
std::array<vertex_t, 6> rect {
request.vertices[0],
request.vertices[1],
request.vertices[2],
request.vertices[0],
request.vertices[2],
request.vertices[3],
};
request.upload_brush_vertices(rect);
} else {
request.upload_brush_vertices(request.vertices);
}
request.draw_brush_shape();
if (request.copy_stroke_destination) {
request.unbind_destination_texture();
}
result.ok = true;
return result;
}
[[nodiscard]] inline LegacyStrokeSampleExecutionResult execute_legacy_canvas_stroke_sample_polygon(
const LegacyStrokeSamplePolygonExecutionRequest& request)
{
std::vector<vertex_t> sample_vertices(
request.polygon_vertices.begin(),
request.polygon_vertices.end());
if (sample_vertices.size() != 3 && sample_vertices.size() != 4) {
sample_vertices = triangulate_simple(sample_vertices);
}
std::vector<pp::paint_renderer::CanvasStrokePoint> sample_points;
sample_points.reserve(sample_vertices.size());
for (const auto& vertex : sample_vertices) {
sample_points.push_back(pp::paint_renderer::CanvasStrokePoint {
.x = vertex.pos.x,
.y = vertex.pos.y,
});
}
return execute_legacy_canvas_stroke_sample(
LegacyStrokeSampleExecutionRequest {
.context = request.context,
.target_size = request.target_size,
.vertices = sample_vertices,
.sample_points = sample_points,
.copy_stroke_destination = request.copy_stroke_destination,
.bind_destination_texture = request.bind_destination_texture,
.copy_framebuffer_to_destination_texture =
request.copy_framebuffer_to_destination_texture,
.unbind_destination_texture = request.unbind_destination_texture,
.upload_brush_vertices = request.upload_brush_vertices,
.draw_brush_shape = request.draw_brush_shape,
});
}
[[nodiscard]] inline LegacyStrokeSampleExecutionResult execute_legacy_canvas_stroke_face_sample_polygon(
const LegacyStrokeFaceSamplePolygonExecutionRequest& request)
{
if (!request.upload_brush_vertices || !request.draw_brush_shape) {
return {};
}
if (request.copy_stroke_destination &&
(!request.bind_destination_texture ||
!request.copy_framebuffer_to_destination_texture ||
!request.unbind_destination_texture)) {
return {};
}
return execute_legacy_canvas_stroke_sample_polygon(
LegacyStrokeSamplePolygonExecutionRequest {
.context = request.context,
.target_size = request.target_size,
.polygon_vertices = request.polygon_vertices,
.copy_stroke_destination = request.copy_stroke_destination,
.bind_destination_texture = [&] {
request.bind_destination_texture(request.face_index);
},
.copy_framebuffer_to_destination_texture =
[&](int src_x, int src_y, int dst_x, int dst_y, int width, int height) {
request.copy_framebuffer_to_destination_texture(
request.face_index,
src_x,
src_y,
dst_x,
dst_y,
width,
height);
},
.unbind_destination_texture = [&] {
request.unbind_destination_texture(request.face_index);
},
.upload_brush_vertices = [&](std::span<const vertex_t> vertices) {
request.upload_brush_vertices(request.face_index, vertices);
},
.draw_brush_shape = [&] {
request.draw_brush_shape(request.face_index);
},
});
}
[[nodiscard]] inline LegacyCanvasStrokeMixPassResult execute_legacy_canvas_stroke_mix_pass(
const LegacyCanvasStrokeMixPassRequest& request)
{
LegacyCanvasStrokeMixPassResult result;
if (request.resolution.x <= 0.0f ||
request.resolution.y <= 0.0f ||
!request.setup_plane_shader ||
!request.bind_layer_texture ||
!request.bind_stroke_texture ||
!request.bind_mask_texture ||
!request.draw_plane ||
!request.unbind_mask_texture ||
!request.unbind_stroke_texture ||
!request.unbind_layer_texture) {
return result;
}
if (request.bind_mix_samplers) {
request.bind_mix_samplers();
}
for (const auto& plane : request.planes) {
if (!plane.visible || plane.opacity <= 0.0f || !plane.has_target) {
continue;
}
request.setup_plane_shader(plane.index, plane.mvp);
request.bind_layer_texture(plane.index);
request.bind_stroke_texture(plane.index);
request.bind_mask_texture(plane.index);
request.draw_plane();
request.unbind_mask_texture(plane.index);
request.unbind_stroke_texture(plane.index);
request.unbind_layer_texture(plane.index);
++result.composed_planes;
}
if (request.unbind_mix_samplers) {
request.unbind_mix_samplers();
}
result.ok = true;
return result;
}
} // namespace pp::panopainter