#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 #include #include #include #include #include #include namespace pp::panopainter { struct LegacyStrokeSampleExecutionRequest { std::string_view context; glm::vec2 target_size {}; std::span vertices; std::span sample_points; bool copy_stroke_destination = false; std::function bind_destination_texture; std::function copy_framebuffer_to_destination_texture; std::function unbind_destination_texture; std::function)> upload_brush_vertices; std::function 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 polygon_vertices; bool copy_stroke_destination = false; std::function bind_destination_texture; std::function copy_framebuffer_to_destination_texture; std::function unbind_destination_texture; std::function)> upload_brush_vertices; std::function draw_brush_shape; }; struct LegacyStrokeFaceSamplePolygonExecutionRequest { std::string_view context; glm::vec2 target_size {}; std::span polygon_vertices; int face_index = 0; bool copy_stroke_destination = false; std::function bind_destination_texture; std::function copy_framebuffer_to_destination_texture; std::function unbind_destination_texture; std::function)> upload_brush_vertices; std::function 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 activate_texture_unit; std::function bind_brush_tip; std::function unbind_brush_tip; std::function bind_stroke_destination; std::function unbind_stroke_destination; std::function bind_pattern; std::function unbind_pattern; std::function bind_mixer; std::function unbind_mixer; }; [[nodiscard]] inline LegacyCanvasStrokeTextureInputDispatch make_legacy_canvas_stroke_main_pass_texture_dispatch( std::function activate_texture_unit, std::function bind_brush_tip, std::function unbind_brush_tip, std::function bind_pattern, std::function bind_mixer, std::function unbind_mixer) { return LegacyCanvasStrokeTextureInputDispatch { .activate_texture_unit = std::move(activate_texture_unit), .bind_brush_tip = std::move(bind_brush_tip), .unbind_brush_tip = std::move(unbind_brush_tip), .bind_pattern = std::move(bind_pattern), .bind_mixer = std::move(bind_mixer), .unbind_mixer = std::move(unbind_mixer), }; } [[nodiscard]] inline LegacyCanvasStrokeTextureInputDispatch make_legacy_canvas_stroke_brush_tip_texture_dispatch( std::function activate_texture_unit, std::function bind_brush_tip, std::function unbind_brush_tip) { return LegacyCanvasStrokeTextureInputDispatch { .activate_texture_unit = std::move(activate_texture_unit), .bind_brush_tip = std::move(bind_brush_tip), .unbind_brush_tip = std::move(unbind_brush_tip), }; } [[nodiscard]] inline LegacyCanvasStrokeTextureInputDispatch make_legacy_canvas_stroke_brush_tip_texture_dispatch( std::function activate_texture_unit, std::function bind_brush_tip, std::function unbind_brush_tip, int face_index) { return make_legacy_canvas_stroke_brush_tip_texture_dispatch( std::move(activate_texture_unit), [bind_brush_tip = std::move(bind_brush_tip), face_index]() { bind_brush_tip(face_index); }, [unbind_brush_tip = std::move(unbind_brush_tip), face_index]() { unbind_brush_tip(face_index); }); } [[nodiscard]] inline LegacyCanvasStrokeTextureInputDispatch make_legacy_canvas_stroke_destination_texture_dispatch( std::function activate_texture_unit, std::function bind_stroke_destination, std::function unbind_stroke_destination) { return LegacyCanvasStrokeTextureInputDispatch { .activate_texture_unit = std::move(activate_texture_unit), .bind_stroke_destination = std::move(bind_stroke_destination), .unbind_stroke_destination = std::move(unbind_stroke_destination), }; } [[nodiscard]] inline LegacyCanvasStrokeTextureInputDispatch make_legacy_canvas_stroke_pad_destination_texture_dispatch( std::function activate_texture_unit, std::function bind_stroke_destination, std::function unbind_stroke_destination) { return LegacyCanvasStrokeTextureInputDispatch { .activate_texture_unit = std::move(activate_texture_unit), .bind_stroke_destination = std::move(bind_stroke_destination), .unbind_stroke_destination = std::move(unbind_stroke_destination), }; } [[nodiscard]] inline LegacyCanvasStrokeTextureInputDispatch make_legacy_canvas_stroke_pad_destination_texture_dispatch( std::function activate_texture_unit, std::function bind_stroke_destination, std::function unbind_stroke_destination, int face_index) { return make_legacy_canvas_stroke_pad_destination_texture_dispatch( std::move(activate_texture_unit), [bind_stroke_destination = std::move(bind_stroke_destination), face_index]() { bind_stroke_destination(face_index); }, [unbind_stroke_destination = std::move(unbind_stroke_destination), face_index]() { unbind_stroke_destination(face_index); }); } struct LegacyCanvasStrokeSamplerDispatch { std::function bind_brush_tip_sampler; std::function unbind_brush_tip_sampler; std::function bind_stroke_destination_sampler; std::function unbind_stroke_destination_sampler; std::function bind_pattern_sampler; std::function unbind_pattern_sampler; std::function bind_mixer_sampler; std::function unbind_mixer_sampler; }; struct LegacyCanvasStrokeMainPassExecutionRequest { std::string_view context; std::function bind_samplers; std::function bind_textures; std::function execute_frame_pass; std::function unbind_textures; std::function unbind_samplers; }; [[nodiscard]] inline LegacyCanvasStrokeSamplerDispatch make_legacy_canvas_stroke_live_pass_sampler_dispatch( std::function bind_brush_tip_sampler, std::function unbind_brush_tip_sampler, std::function bind_stroke_destination_sampler, std::function unbind_stroke_destination_sampler, std::function bind_pattern_sampler, std::function unbind_pattern_sampler, std::function bind_mixer_sampler, std::function unbind_mixer_sampler) { return LegacyCanvasStrokeSamplerDispatch { .bind_brush_tip_sampler = std::move(bind_brush_tip_sampler), .unbind_brush_tip_sampler = std::move(unbind_brush_tip_sampler), .bind_stroke_destination_sampler = std::move(bind_stroke_destination_sampler), .unbind_stroke_destination_sampler = std::move(unbind_stroke_destination_sampler), .bind_pattern_sampler = std::move(bind_pattern_sampler), .unbind_pattern_sampler = std::move(unbind_pattern_sampler), .bind_mixer_sampler = std::move(bind_mixer_sampler), .unbind_mixer_sampler = std::move(unbind_mixer_sampler), }; } [[nodiscard]] inline LegacyCanvasStrokeSamplerDispatch make_legacy_canvas_stroke_sampler_dispatch( std::function bind_brush_tip_sampler, std::function unbind_brush_tip_sampler, std::function bind_stroke_destination_sampler, std::function unbind_stroke_destination_sampler, std::function bind_pattern_sampler, std::function unbind_pattern_sampler, std::function bind_mixer_sampler, std::function unbind_mixer_sampler) { return LegacyCanvasStrokeSamplerDispatch { .bind_brush_tip_sampler = std::move(bind_brush_tip_sampler), .unbind_brush_tip_sampler = std::move(unbind_brush_tip_sampler), .bind_stroke_destination_sampler = std::move(bind_stroke_destination_sampler), .unbind_stroke_destination_sampler = std::move(unbind_stroke_destination_sampler), .bind_pattern_sampler = std::move(bind_pattern_sampler), .unbind_pattern_sampler = std::move(unbind_pattern_sampler), .bind_mixer_sampler = std::move(bind_mixer_sampler), .unbind_mixer_sampler = std::move(unbind_mixer_sampler), }; } [[nodiscard]] inline bool execute_legacy_canvas_stroke_main_pass( const LegacyCanvasStrokeMainPassExecutionRequest& request) { if (!request.bind_samplers || !request.bind_textures || !request.execute_frame_pass || !request.unbind_textures || !request.unbind_samplers) { return false; } request.bind_samplers(); request.bind_textures(); request.execute_frame_pass(); request.unbind_textures(); request.unbind_samplers(); return true; } 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 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 faces; bool copy_stroke_destination = false; std::function)> upload_pad_vertices; std::function begin_face; std::function bind_destination_texture; std::function copy_framebuffer_to_destination_texture; std::function unbind_destination_texture; std::function draw_pad; std::function finish_face; std::function)> execute_face; }; struct LegacyCanvasStrokePadExecutionResult { bool ok = false; std::size_t padded_faces = 0; }; [[nodiscard]] inline LegacyCanvasStrokePadExecutionResult execute_legacy_canvas_stroke_pad_faces( const LegacyCanvasStrokePadExecutionRequest& request); 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 planes; std::function bind_mix_samplers; std::function unbind_mix_samplers; std::function setup_plane_shader; std::function bind_layer_texture; std::function bind_stroke_texture; std::function bind_mask_texture; std::function draw_plane; std::function unbind_mask_texture; std::function unbind_stroke_texture; std::function unbind_layer_texture; }; struct LegacyCanvasStrokeMixPassResult { bool ok = false; std::size_t composed_planes = 0; }; struct LegacyCanvasStrokeMixPassSetup { std::function begin; std::function end; }; [[nodiscard]] inline LegacyCanvasStrokeMixPassSetup make_legacy_canvas_stroke_mix_pass_setup( std::function begin, std::function end) { return LegacyCanvasStrokeMixPassSetup { .begin = std::move(begin), .end = std::move(end), }; } [[nodiscard]] inline LegacyCanvasStrokeMixPassRequest make_legacy_canvas_stroke_mix_pass_request( std::string_view context, glm::vec2 resolution, std::span planes, std::function bind_mix_samplers, std::function unbind_mix_samplers, std::function setup_plane_shader, std::function bind_layer_texture, std::function bind_stroke_texture, std::function bind_mask_texture, std::function draw_plane, std::function unbind_mask_texture, std::function unbind_stroke_texture, std::function unbind_layer_texture) { return LegacyCanvasStrokeMixPassRequest { .context = context, .resolution = resolution, .planes = planes, .bind_mix_samplers = std::move(bind_mix_samplers), .unbind_mix_samplers = std::move(unbind_mix_samplers), .setup_plane_shader = std::move(setup_plane_shader), .bind_layer_texture = std::move(bind_layer_texture), .bind_stroke_texture = std::move(bind_stroke_texture), .bind_mask_texture = std::move(bind_mask_texture), .draw_plane = std::move(draw_plane), .unbind_mask_texture = std::move(unbind_mask_texture), .unbind_stroke_texture = std::move(unbind_stroke_texture), .unbind_layer_texture = std::move(unbind_layer_texture), }; } struct LegacyCanvasStrokeMixPassShell { LegacyCanvasStrokeMixPassSetup setup; LegacyCanvasStrokeMixPassRequest request; }; template [[nodiscard]] inline LegacyCanvasStrokeMixPassShell make_legacy_canvas_stroke_mix_pass_shell( BeginMixPass&& begin_mix_pass, EndMixPass&& end_mix_pass, std::string_view context, glm::vec2 resolution, std::span planes, std::function bind_mix_samplers, std::function unbind_mix_samplers, std::function setup_plane_shader, std::function bind_layer_texture, std::function bind_stroke_texture, std::function bind_mask_texture, std::function draw_plane, std::function unbind_mask_texture, std::function unbind_stroke_texture, std::function unbind_layer_texture) { return LegacyCanvasStrokeMixPassShell { .setup = make_legacy_canvas_stroke_mix_pass_setup( std::forward(begin_mix_pass), std::forward(end_mix_pass)), .request = make_legacy_canvas_stroke_mix_pass_request( context, resolution, planes, std::move(bind_mix_samplers), std::move(unbind_mix_samplers), std::move(setup_plane_shader), std::move(bind_layer_texture), std::move(bind_stroke_texture), std::move(bind_mask_texture), std::move(draw_plane), std::move(unbind_mask_texture), std::move(unbind_stroke_texture), std::move(unbind_layer_texture)), }; } struct LegacyCanvasStrokeDualPassRequest { std::string_view context; std::function bind_brush_tip; std::function unbind_brush_tip; std::function setup_dual_shader; std::function execute_frame_pass; }; struct LegacyCanvasStrokeDualPassResult { bool ok = false; std::size_t composed_planes = 0; }; [[nodiscard]] inline LegacyCanvasStrokeMixPassResult execute_legacy_canvas_stroke_mix_pass( const LegacyCanvasStrokeMixPassRequest& request); template std::size_t execute_legacy_canvas_stroke_live_pass_with_face_framebuffers( Frames&& frames, pp::renderer::Extent2D extent, std::span accumulated_dirty_boxes, std::span pass_dirty_boxes, std::span 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 committed_dirty_faces = {}, std::span pass_dirty_faces = {}); template std::size_t execute_legacy_canvas_stroke_dual_pass_frame_callbacks( Frames&& frames, pp::renderer::Extent2D extent, std::span accumulated_dirty_boxes, std::span pass_dirty_boxes, std::span 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 committed_dirty_faces = {}, std::span pass_dirty_faces = {}) { return execute_legacy_canvas_stroke_live_pass_with_face_framebuffers( std::forward(frames), extent, accumulated_dirty_boxes, pass_dirty_boxes, include_in_committed_dirty_box, std::forward(begin_frame), std::forward(prepare_face), std::forward(execute_sample), face_framebuffers, preserve_sample_dirty_as_pass_dirty, committed_dirty_faces, pass_dirty_faces); } template std::size_t execute_legacy_canvas_stroke_main_pass_frame_callbacks( Frames&& frames, pp::renderer::Extent2D extent, std::span accumulated_dirty_boxes, std::span pass_dirty_boxes, std::span 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 committed_dirty_faces = {}, std::span pass_dirty_faces = {}) { return execute_legacy_canvas_stroke_live_pass_with_face_framebuffers( std::forward(frames), extent, accumulated_dirty_boxes, pass_dirty_boxes, include_in_committed_dirty_box, std::forward(begin_frame), std::forward(prepare_face), std::forward(execute_sample), face_framebuffers, preserve_sample_dirty_as_pass_dirty, committed_dirty_faces, pass_dirty_faces); } template std::size_t execute_legacy_canvas_stroke_pad_face_callbacks( Faces&& faces, pp::renderer::Extent2D extent, bool copy_stroke_destination, UploadPadVertices&& upload_pad_vertices, BeginFace&& begin_face, BindDestinationTexture&& bind_destination_texture, CopyFramebufferToDestinationTexture&& copy_framebuffer_to_destination_texture, UnbindDestinationTexture&& unbind_destination_texture, DrawPad&& draw_pad, FinishFace&& finish_face) { return execute_legacy_canvas_stroke_pad_faces( LegacyCanvasStrokePadExecutionRequest { .context = "Canvas::stroke_draw", .extent = extent, .faces = std::forward(faces), .copy_stroke_destination = copy_stroke_destination, .upload_pad_vertices = std::forward(upload_pad_vertices), .begin_face = std::forward(begin_face), .bind_destination_texture = std::forward(bind_destination_texture), .copy_framebuffer_to_destination_texture = std::forward(copy_framebuffer_to_destination_texture), .unbind_destination_texture = std::forward(unbind_destination_texture), .draw_pad = std::forward(draw_pad), .finish_face = std::forward(finish_face), }).padded_faces; } [[nodiscard]] inline LegacyCanvasStrokeDualPassResult execute_legacy_canvas_stroke_dual_pass( const LegacyCanvasStrokeDualPassRequest& request) { LegacyCanvasStrokeDualPassResult result; if (!request.setup_dual_shader || !request.bind_brush_tip || !request.unbind_brush_tip || !request.execute_frame_pass) { return result; } request.setup_dual_shader(); request.bind_brush_tip(); request.execute_frame_pass(); request.unbind_brush_tip(); result.ok = true; return result; } template [[nodiscard]] inline std::array plan_legacy_canvas_stroke_mix_pass_planes( bool visible, float opacity, const glm::mat4& mvp_base, const std::array& plane_transforms, HasTarget&& has_target, const glm::mat4& plane_offset = glm::translate(glm::vec3(0.0f, 0.0f, -1.0f))) { std::array planes {}; for (std::size_t plane_index = 0; plane_index < PlaneCount; ++plane_index) { planes[plane_index] = LegacyCanvasStrokeMixPassPlane { .index = static_cast(plane_index), .visible = visible, .has_target = static_cast(has_target(static_cast(plane_index))), .opacity = opacity, .mvp = mvp_base * plane_transforms[plane_index] * plane_offset, }; } return planes; } template [[nodiscard]] inline LegacyCanvasStrokeMixPassResult execute_legacy_canvas_stroke_mix_pass_with_setup( BeginMixPass&& begin_mix_pass, EndMixPass&& end_mix_pass, const LegacyCanvasStrokeMixPassRequest& request) { begin_mix_pass(); const auto result = execute_legacy_canvas_stroke_mix_pass(request); end_mix_pass(); return result; } template [[nodiscard]] inline LegacyCanvasStrokeMixPassResult execute_legacy_canvas_stroke_mix_pass_shell( BeginMixPass&& begin_mix_pass, EndMixPass&& end_mix_pass, const LegacyCanvasStrokeMixPassRequest& request) { return execute_legacy_canvas_stroke_mix_pass_with_setup( std::forward(begin_mix_pass), std::forward(end_mix_pass), request); } struct LegacyCanvasStrokeComputeRequest { StrokeSample previous_sample {}; std::span samples; float zoom = 1.0f; glm::vec2 mixer_size {}; glm::mat4 model_view { 1.0f }; }; template [[nodiscard]] auto plan_legacy_canvas_stroke_frames( const LegacyCanvasStrokeComputeRequest& request, ProjectStroke&& project_stroke, MakeFrame&& make_frame) { using StrokeFrame = decltype(make_frame( std::declval(), std::declval(), 0.0f, 0.0f, project_stroke(std::declval&>(), false, request.model_view))); std::vector frames; StrokeSample prev = request.previous_sample; std::array 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 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 requires std::invocable inline void bind_legacy_canvas_stroke_texture_inputs( const std::array& bindings, BindTextureInput&& bind_texture_input) { for (const auto& binding : bindings) { bind_texture_input(binding.input, binding.slot); } } template requires std::invocable inline void unbind_legacy_canvas_stroke_texture_inputs( const std::array& 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 inline void bind_legacy_canvas_stroke_texture_inputs( const std::array& 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 inline void unbind_legacy_canvas_stroke_texture_inputs( const std::array& 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 inline void bind_legacy_canvas_stroke_sampler_inputs( const std::array& bindings, const LegacyCanvasStrokeSamplerDispatch& dispatch) { for (const auto& binding : bindings) { bind_legacy_canvas_stroke_sampler_input(binding.input, binding.slot, dispatch); } } template inline void unbind_legacy_canvas_stroke_sampler_inputs( const std::array& bindings, const LegacyCanvasStrokeSamplerDispatch& dispatch) { for (const auto& binding : bindings) { unbind_legacy_canvas_stroke_sampler_input(binding.input, dispatch); } } template 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 accumulated_dirty_boxes, std::span pass_dirty_boxes, std::span include_in_committed_dirty_box, BeginFrame&& begin_frame, BeginFace&& begin_face, ExecuteSample&& execute_sample, PrepareDirtyRequest&& prepare_dirty_request, FinishFace&& finish_face, std::span committed_dirty_faces = {}, std::span 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 std::size_t execute_legacy_canvas_stroke_live_pass_with_dirty_tracking( Frames&& frames, pp::renderer::Extent2D extent, std::span accumulated_dirty_boxes, std::span pass_dirty_boxes, std::span 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 committed_dirty_faces = {}, std::span pass_dirty_faces = {}) { return execute_legacy_canvas_stroke_frame_samples_with_dirty_tracking( std::forward(frames), extent, accumulated_dirty_boxes, pass_dirty_boxes, include_in_committed_dirty_box, std::forward(begin_frame), std::forward(begin_face), std::forward(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 std::size_t execute_legacy_canvas_stroke_live_pass_with_face_framebuffers( Frames&& frames, pp::renderer::Extent2D extent, std::span accumulated_dirty_boxes, std::span pass_dirty_boxes, std::span include_in_committed_dirty_box, BeginFrame&& begin_frame, PrepareFace&& prepare_face, ExecuteSample&& execute_sample, Framebuffers& face_framebuffers, bool preserve_sample_dirty_as_pass_dirty, std::span committed_dirty_faces, std::span pass_dirty_faces) { return execute_legacy_canvas_stroke_live_pass_with_dirty_tracking( std::forward(frames), extent, accumulated_dirty_boxes, pass_dirty_boxes, include_in_committed_dirty_box, std::forward(begin_frame), [&](auto& frame, int face_index, auto& vertices) { prepare_face(frame, face_index, vertices); face_framebuffers[face_index].bindFramebuffer(); }, std::forward(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 [[nodiscard]] inline std::array make_legacy_canvas_stroke_pad_faces( const std::array& dirty_faces, const std::array& pass_dirty_boxes) noexcept { std::array faces {}; for (std::size_t face_index = 0; face_index < FaceCount; ++face_index) { faces[face_index].index = static_cast(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 [[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 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(request.target_size.x), .height = static_cast(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 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 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 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 vertices) { request.upload_brush_vertices(request.face_index, vertices); }, .draw_brush_shape = [&] { request.draw_brush_shape(request.face_index); }, }); } template [[nodiscard]] inline LegacyStrokeSampleExecutionResult execute_legacy_canvas_stroke_face_sample_polygon( const LegacyStrokeFaceSamplePolygonExecutionRequest& request, const std::array& destination_texture_bindings, const LegacyCanvasStrokeTextureInputDispatch& destination_texture_dispatch) { return execute_legacy_canvas_stroke_face_sample_polygon( LegacyStrokeFaceSamplePolygonExecutionRequest { .context = request.context, .target_size = request.target_size, .polygon_vertices = request.polygon_vertices, .face_index = request.face_index, .copy_stroke_destination = request.copy_stroke_destination, .bind_destination_texture = [&](int) { bind_legacy_canvas_stroke_texture_inputs( destination_texture_bindings, destination_texture_dispatch); }, .copy_framebuffer_to_destination_texture = request.copy_framebuffer_to_destination_texture, .unbind_destination_texture = [&](int) { unbind_legacy_canvas_stroke_texture_inputs( destination_texture_bindings, destination_texture_dispatch); }, .upload_brush_vertices = request.upload_brush_vertices, .draw_brush_shape = request.draw_brush_shape, }); } [[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