#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; }; 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 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; }; 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 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 = false, 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); }, }); } [[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