1080 lines
38 KiB
C++
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
|