#include "pch.h"

#include "legacy_canvas_render_shell_services.h"

#include "app.h"
#include "legacy_canvas_draw_merge_services.h"
#include "legacy_canvas_stroke_commit_services.h"
#include "legacy_canvas_stroke_composite_services.h"
#include "legacy_canvas_stroke_erase_services.h"
#include "legacy_canvas_stroke_execution_services.h"
#include "legacy_canvas_stroke_runtime_services.h"
#include "legacy_canvas_stroke_shader_services.h"
#include "legacy_canvas_projection_services.h"
#include "legacy_ui_gl_dispatch.h"
#include "renderer_gl/opengl_capabilities.h"
#include "util.h"

#include <algorithm>
#include <array>
#include <chrono>
#include <cstdint>
#include <utility>

namespace {

using namespace pp::panopainter;

GLint current_canvas_stroke_internal_format()
{
    const auto renderer_features = ShaderManager::render_device_features();
    if (renderer_features.float32_linear_filtering)
        return static_cast<GLint>(pp::renderer::gl::rgba32f_internal_format());
    if (renderer_features.float16_render_targets)
        return static_cast<GLint>(pp::renderer::gl::rgba16f_internal_format());
    return static_cast<GLint>(pp::renderer::gl::rgba8_internal_format());
}

GLint rgba8_internal_format()
{
    return static_cast<GLint>(pp::renderer::gl::rgba8_internal_format());
}

GLenum depth_test_state()
{
    return static_cast<GLenum>(pp::renderer::gl::depth_test_state());
}

GLenum blend_state()
{
    return static_cast<GLenum>(pp::renderer::gl::blend_state());
}

void set_active_texture_unit(std::uint32_t unit_index)
{
    pp::legacy::ui_gl::activate_texture_unit(unit_index, "Canvas");
}

void unbind_texture_2d()
{
    pp::legacy::ui_gl::unbind_texture_2d("Canvas");
}

void apply_canvas_viewport(std::int32_t x, std::int32_t y, std::int32_t width, std::int32_t height)
{
    pp::legacy::ui_gl::apply_viewport(x, y, width, height, "Canvas");
}

void apply_canvas_capability(std::uint32_t state, bool enabled)
{
    pp::legacy::ui_gl::set_capability(state, enabled, "Canvas");
}

pp::paint_renderer::CanvasBlendGatePlan draw_merge_blend_gate_plan(
    int width,
    int height,
    const std::vector<std::shared_ptr<Layer>>& layers,
    const Brush* brush) noexcept
{
    std::vector<int> layer_blend_modes;
    layer_blend_modes.reserve(layers.size());
    for (const auto& layer : layers) {
        if (!layer) {
            continue;
        }
        layer_blend_modes.push_back(layer->m_blend_mode);
    }

    const auto plan = pp::paint_renderer::plan_canvas_blend_gate(
        ShaderManager::render_device_features(),
        pp::paint_renderer::CanvasBlendGateRequest {
            .extent = pp::renderer::Extent2D {
                .width = static_cast<std::uint32_t>(std::max(width, 0)),
                .height = static_cast<std::uint32_t>(std::max(height, 0)),
            },
            .layer_blend_modes = layer_blend_modes,
            .has_stroke_blend_mode = brush != nullptr,
            .stroke_blend_mode = brush ? brush->m_blend_mode : 0,
        });
    if (plan) {
        return plan.value();
    }

    pp::paint_renderer::CanvasBlendGatePlan fallback;
    fallback.shader_blend = true;
    fallback.complex_blend = true;
    fallback.compatibility_fallback = true;
    return fallback;
}

pp::panopainter::LegacyCanvasDrawMergeFinalPlaneCompositeExecution
make_canvas_draw_merge_final_plane_composite_execution(Canvas& canvas)
{
    return {
        .bind_merged_texture_copy_target = [&] {
            set_active_texture_unit(2);
            canvas.m_merge_tex.bind();
        },
        .copy_merged_framebuffer = [&] {
            copy_framebuffer_to_texture_2d(0, 0, 0, 0, canvas.m_width, canvas.m_height);
        },
        .enable_blend = [&] {
            apply_canvas_capability(blend_state(), true);
        },
        .draw = [&] {
            canvas.m_plane.draw_fill();
        },
        .bind_sampler = [&] {
            canvas.m_sampler.bind(0);
        },
        .bind_merged_texture = [&] {
            set_active_texture_unit(0);
            canvas.m_merge_tex.bind();
        },
        .unbind_merged_texture = [&] {
            canvas.m_merge_tex.unbind();
        },
    };
}

static void execute_canvas_draw_merge_temporary_erase_dispatch(
    Canvas& canvas,
    int plane_index,
    int layer_index,
    const std::shared_ptr<Layer>& layer,
    const glm::mat4& ortho);

static pp::panopainter::LegacyCanvasDrawMergeTemporaryCompositeExecution
make_canvas_draw_merge_temporary_paint_request(
    Canvas& canvas,
    int layer_index,
    int plane_index,
    const std::shared_ptr<Layer>& layer,
    const Brush& brush,
    const glm::mat4& ortho);

static pp::panopainter::LegacyCanvasDrawMergeLayerTextureExecution
make_canvas_draw_merge_layer_texture_dispatch(
    Canvas& canvas,
    int plane_index,
    int layer_index);

static pp::panopainter::LegacyCanvasDrawMergeLayerBlendExecution
make_canvas_draw_merge_layer_blend_dispatch(Canvas& canvas);

static void execute_canvas_draw_merge_branch_body(
    Canvas& canvas,
    int plane_index,
    int layer_index,
    const std::shared_ptr<Layer>& layer,
    const Brush& brush,
    const glm::mat4& ortho,
    bool use_blend,
    bool copy_blend_destination)
{
    if (use_blend)
    {
        canvas.m_merge_rtt.bindFramebuffer();
        canvas.m_merge_rtt.clear();
    }

    pp::panopainter::execute_legacy_canvas_draw_merge_layer_composite(
        canvas.m_current_stroke && canvas.m_current_mode == kCanvasMode::Erase && canvas.m_show_tmp && canvas.m_current_layer_idx == layer_index,
        canvas.m_current_stroke && canvas.m_show_tmp && canvas.m_current_layer_idx == layer_index,
        use_blend,
        pp::panopainter::LegacyCanvasDrawMergeLayerCompositeExecution {
            .execute_temporary_erase = [&] {
                execute_canvas_draw_merge_temporary_erase_dispatch(
                    canvas,
                    plane_index,
                    layer_index,
                    layer,
                    ortho);
            },
            .execute_temporary_paint = [&] {
                pp::panopainter::execute_legacy_canvas_draw_merge_temporary_composite(
                    make_canvas_draw_merge_temporary_paint_request(
                        canvas,
                        layer_index,
                        plane_index,
                        layer,
                        brush,
                        ortho));
            },
            .execute_layer_texture = [&] {
                pp::panopainter::execute_legacy_canvas_draw_merge_layer_texture(
                    pp::panopainter::LegacyCanvasDrawMergeTextureAlphaUniforms {
                        .mvp = ortho,
                        .texture_slot = 0,
                        .alpha = layer->m_opacity,
                        .highlight = layer->m_hightlight,
                    },
                    make_canvas_draw_merge_layer_texture_dispatch(
                        canvas,
                        plane_index,
                        layer_index));
            },
            .execute_layer_blend = [&] {
                pp::panopainter::execute_legacy_canvas_draw_merge_layer_blend(
                    pp::panopainter::LegacyCanvasDrawMergeLayerBlendUniforms {
                        .shader = {
                            .mvp = ortho,
                            .texture_slot = 0,
                            .destination_texture_slot = 2,
                            .use_destination_texture = copy_blend_destination,
                            .blend_mode = layer->m_blend_mode,
                            .alpha = 1.f,
                        },
                        .copy_destination = copy_blend_destination,
                    },
                    make_canvas_draw_merge_layer_blend_dispatch(
                        canvas));
            },
        });
}

static void execute_canvas_draw_merge_plane_final_composite(
    Canvas& canvas,
    const glm::mat4& ortho,
    bool draw_checkerboard,
    bool use_blend)
{
    if (use_blend)
    {
        pp::panopainter::execute_legacy_canvas_draw_merge_final_plane_composite(
            pp::panopainter::LegacyCanvasDrawMergeFinalPlaneCompositeUniforms {
                .checkerboard = {
                    .mvp = ortho,
                    .colorize = false,
                },
                .texture = {
                    .mvp = ortho,
                    .texture_slot = 0,
                },
                .draw_checkerboard = draw_checkerboard,
            },
            make_canvas_draw_merge_final_plane_composite_execution(canvas));
    }
}

static void execute_canvas_draw_merge_plane_dispatch(
    Canvas& canvas,
    int plane_index,
    const std::vector<std::shared_ptr<Layer>>& layers,
    const Brush& brush,
    const glm::mat4& ortho,
    bool use_blend,
    bool copy_blend_destination,
    bool draw_checkerboard)
{
    canvas.m_layers_merge.rtt(plane_index).bindFramebuffer();
    pp::panopainter::execute_legacy_canvas_draw_merge_plane_setup(
        pp::panopainter::LegacyCanvasDrawMergePlaneSetupUniforms {
            .checkerboard = {
                .mvp = ortho,
                .colorize = false,
            },
            .use_blend = use_blend,
            .draw_checkerboard = draw_checkerboard,
        },
        pp::panopainter::LegacyCanvasDrawMergePlaneSetupExecution {
            .clear_plane = [&] {
                canvas.m_layers_merge.rtt(plane_index).clear({ 1, 1, 1, 0 });
            },
            .disable_blend = [&] {
                apply_canvas_capability(blend_state(), false);
            },
            .enable_blend = [&] {
                apply_canvas_capability(blend_state(), true);
            },
            .draw = [&] {
                canvas.m_plane.draw_fill();
            },
        });

    for (int layer_index = 0; layer_index < layers.size(); layer_index++)
    {
        execute_canvas_draw_merge_branch_body(
            canvas,
            plane_index,
            layer_index,
            layers[layer_index],
            brush,
            ortho,
            use_blend,
            copy_blend_destination);
    }

    execute_canvas_draw_merge_plane_final_composite(canvas, ortho, draw_checkerboard, use_blend);
    canvas.m_layers_merge.rtt(plane_index).unbindFramebuffer();
}

static void execute_canvas_draw_merge_plane_iteration(
    Canvas& canvas,
    const std::array<bool, 6>& faces,
    const std::vector<std::shared_ptr<Layer>>& layers,
    const Brush& brush,
    const glm::mat4& ortho,
    bool use_blend,
    bool copy_blend_destination,
    bool draw_checkerboard)
{
    for (int plane_index = 0; plane_index < 6; plane_index++)
    {
        if (!faces[plane_index])
            continue;

        execute_canvas_draw_merge_plane_dispatch(
            canvas,
            plane_index,
            layers,
            brush,
            ortho,
            use_blend,
            copy_blend_destination,
            draw_checkerboard);
    }
}

static void execute_canvas_draw_merge_temporary_erase_dispatch(
    Canvas& canvas,
    int plane_index,
    int layer_index,
    const std::shared_ptr<Layer>& layer,
    const glm::mat4& ortho)
{
    pp::panopainter::execute_legacy_canvas_draw_merge_temporary_composite(
        pp::panopainter::make_legacy_canvas_draw_merge_temporary_erase_composite(
            [&] {
                pp::panopainter::setup_legacy_stroke_erase_shader(
                    pp::panopainter::LegacyStrokeEraseUniforms {
                        .mvp = ortho,
                        .texture_slot = 0,
                        .stroke_texture_slot = 1,
                        .mask_texture_slot = 2,
                        .alpha = layer->m_opacity,
                        .mask_enabled = canvas.m_smask_active,
                    });
            },
            [&] {
                canvas.m_sampler.bind(0);
                canvas.m_sampler.bind(1);
                canvas.m_sampler.bind(2);
            },
            [&] {
                set_active_texture_unit(0);
                canvas.m_layers[layer_index]->rtt(plane_index).bindTexture();
                set_active_texture_unit(1);
                canvas.m_tmp[plane_index].bindTexture();
                set_active_texture_unit(2);
                canvas.m_smask.rtt(plane_index).bindTexture();
            },
            [&] {
                canvas.m_plane.draw_fill();
            },
            [&] {
                canvas.m_smask.rtt(plane_index).unbindTexture();
                set_active_texture_unit(1);
                canvas.m_tmp[plane_index].unbindTexture();
                set_active_texture_unit(0);
                canvas.m_layers[layer_index]->rtt(plane_index).unbindTexture();
            }));
}

static pp::panopainter::LegacyCanvasDrawMergeTemporaryCompositeExecution
make_canvas_draw_merge_temporary_paint_request(
    Canvas& canvas,
    int layer_index,
    int plane_index,
    const std::shared_ptr<Layer>& layer,
    const Brush& brush,
    const glm::mat4& ortho)
{
    const auto stroke_material = pp::panopainter::plan_legacy_canvas_stroke_material(
        pp::paint_renderer::CanvasStrokeMaterialRequest {
            .destination_feedback_needed = false,
            .pattern_enabled = brush.m_pattern_enabled,
            .pattern_eachsample = brush.m_pattern_eachsample,
            .wet_blend = brush.m_tip_wet > 0.F,
            .mix_blend = brush.m_tip_mix > 0.F,
            .noise_enabled = brush.m_tip_noise > 0.F,
            .dual_brush_enabled = brush.m_dual_enabled,
            .dual_blend_mode = brush.m_dual_blend_mode,
            .pattern_blend_mode = brush.m_pattern_blend_mode,
            .dual_alpha = brush.m_dual_opacity,
        });
    glm::vec2 patt_scale = glm::vec2(brush.m_pattern_scale);
    if (brush.m_pattern_flipx) patt_scale.x *= -1.f;
    if (brush.m_pattern_flipy) patt_scale.y *= -1.f;

    return pp::panopainter::make_legacy_canvas_draw_merge_temporary_paint_composite(
        [&] {
            pp::panopainter::setup_legacy_stroke_composite_shader(
                pp::panopainter::LegacyStrokeCompositeUniforms {
                    .resolution = Canvas::I->m_size,
                    .pattern = {
                        .scale = patt_scale,
                        .invert = static_cast<float>(brush.m_pattern_invert),
                        .brightness = brush.m_pattern_brightness,
                        .contrast = brush.m_pattern_contrast,
                        .depth = brush.m_pattern_depth,
                        .blend_mode = brush.m_pattern_blend_mode,
                        .offset = Canvas::I->m_pattern_offset,
                    },
                    .mvp = ortho,
                    .layer_alpha = layer->m_opacity,
                    .alpha_lock = layer->m_alpha_locked,
                    .mask_enabled = canvas.m_smask_active,
                    .use_fragcoord = false,
                    .blend_mode = brush.m_blend_mode,
                    .use_dual = stroke_material.composite_pass.use_dual,
                    .dual_blend_mode = stroke_material.composite_pass.dual_blend_mode,
                    .dual_alpha = stroke_material.composite_pass.dual_alpha,
                    .use_pattern = stroke_material.composite_pass.use_pattern,
                });
        },
        [&] {
            canvas.m_sampler.bind(0);
            canvas.m_sampler.bind(1);
            canvas.m_sampler.bind(2);
            canvas.m_sampler.bind(3);
            canvas.m_sampler_stencil.bind(4);
        },
        [&] {
            set_active_texture_unit(0);
            canvas.m_layers[layer_index]->rtt(plane_index).bindTexture();
            set_active_texture_unit(1);
            canvas.m_tmp[plane_index].bindTexture();
            set_active_texture_unit(2);
            canvas.m_smask.rtt(plane_index).bindTexture();
            set_active_texture_unit(3);
            if (stroke_material.composite_pass.use_dual)
                canvas.m_tmp_dual[plane_index].bindTexture();
            set_active_texture_unit(4);
            brush.m_pattern_texture ? brush.m_pattern_texture->bind() : unbind_texture_2d();
        },
        [&] {
            canvas.m_plane.draw_fill();
        },
        [&] {
            set_active_texture_unit(3);
            if (stroke_material.composite_pass.use_dual)
                canvas.m_tmp_dual[plane_index].unbindTexture();
            set_active_texture_unit(2);
            canvas.m_smask.rtt(plane_index).unbindTexture();
            set_active_texture_unit(1);
            canvas.m_tmp[plane_index].unbindTexture();
            set_active_texture_unit(0);
            canvas.m_layers[layer_index]->rtt(plane_index).unbindTexture();
        });
}

static pp::panopainter::LegacyCanvasDrawMergeLayerTextureExecution
make_canvas_draw_merge_layer_texture_dispatch(
    Canvas& canvas,
    int plane_index,
    int layer_index)
{
    return {
        .bind_sampler = [&] {
            canvas.m_cam_fov < 20.f ? canvas.m_sampler_nearest.bind(0) : canvas.m_sampler.bind(0);
        },
        .bind_layer_texture = [&] {
            set_active_texture_unit(0);
            canvas.m_layers[layer_index]->rtt(plane_index).bindTexture();
        },
        .draw = [&] {
            canvas.m_plane.draw_fill();
        },
        .unbind_layer_texture = [&] {
            canvas.m_layers[layer_index]->rtt(plane_index).unbindTexture();
        },
    };
}

static pp::panopainter::LegacyCanvasDrawMergeLayerBlendExecution
make_canvas_draw_merge_layer_blend_dispatch(Canvas& canvas)
{
    return {
        .unbind_merge_framebuffer = [&] {
            canvas.m_merge_rtt.unbindFramebuffer();
        },
        .bind_samplers = [&] {
            canvas.m_sampler.bind(0);
            canvas.m_sampler.bind(2);
        },
        .bind_merge_texture = [&] {
            set_active_texture_unit(0);
            canvas.m_merge_rtt.bindTexture();
        },
        .bind_destination_texture = [&] {
            set_active_texture_unit(2);
            canvas.m_merge_tex.bind();
        },
        .copy_destination_framebuffer = [&] {
            copy_framebuffer_to_texture_2d(0, 0, 0, 0, canvas.m_width, canvas.m_height);
        },
        .draw = [&] {
            canvas.m_plane.draw_fill();
        },
        .unbind_destination_texture = [&] {
            set_active_texture_unit(2);
            canvas.m_merge_tex.unbind();
        },
        .unbind_merge_texture = [&] {
            set_active_texture_unit(0);
            canvas.m_merge_rtt.unbindTexture();
        },
    };
}

} // namespace

namespace pp::panopainter {

void legacy_canvas_draw_merge_temporary_paint_branch(
    Canvas& canvas,
    int layer_index,
    int plane_index,
    std::shared_ptr<Layer> layer,
    const Brush& brush,
    const glm::mat4& ortho)
{
    pp::panopainter::execute_legacy_canvas_draw_merge_temporary_composite(
        make_canvas_draw_merge_temporary_paint_request(
            canvas,
            layer_index,
            plane_index,
            layer,
            brush,
            ortho));
}

void legacy_canvas_draw_merge_branch_orchestration(
    Canvas& canvas,
    int plane_index,
    int layer_index,
    const std::shared_ptr<Layer>& layer,
    const Brush& brush,
    const glm::mat4& ortho,
    bool use_blend,
    bool copy_blend_destination)
{
    if (!(canvas.m_show_tmp && canvas.m_current_layer_idx == layer_index) &&
        (!layer->m_visible ||
            layer->m_opacity == .0f ||
            !layer->face(plane_index)))
        return;
    execute_canvas_draw_merge_branch_body(
        canvas,
        plane_index,
        layer_index,
        layer,
        brush,
        ortho,
        use_blend,
        copy_blend_destination);
}

void legacy_canvas_draw_merge_final_plane_composite(
    Canvas& canvas,
    const glm::mat4& ortho,
    bool draw_checkerboard)
{
    pp::panopainter::execute_legacy_canvas_draw_merge_final_plane_composite(
        pp::panopainter::LegacyCanvasDrawMergeFinalPlaneCompositeUniforms {
            .checkerboard = {
                .mvp = ortho,
                .colorize = false,
            },
            .texture = {
                .mvp = ortho,
                .texture_slot = 0,
            },
            .draw_checkerboard = draw_checkerboard,
        },
        make_canvas_draw_merge_final_plane_composite_execution(canvas));
}

void legacy_canvas_stroke_commit_timelapse(Canvas& canvas)
{
    if (canvas.m_encoder && App::I->rec_running)
    {
        auto t_now = std::chrono::high_resolution_clock::now();
        float dt = std::chrono::duration<float>(t_now - canvas.m_disrty_stroke_time).count();
        if (dt > 2.f && canvas.m_dirty_stroke && App::I->rec_mutex.try_lock())
        {
            legacy_canvas_draw_merge(canvas, true);
            App::I->rec_mutex.unlock();
            App::I->rec_cv.notify_one();
            LOG("rec frame generated");
            canvas.m_dirty_stroke = false;
            canvas.m_disrty_stroke_time = std::chrono::steady_clock::now();
        }
    }
}

void legacy_canvas_draw_merge(Canvas& canvas, bool draw_checkerboard, std::array<bool, 6> faces)
{
    assert(App::I->is_render_thread());

    apply_canvas_viewport(0, 0, canvas.m_width, canvas.m_height);
    auto ortho = glm::ortho<float>(-0.5f, 0.5f, -0.5f, 0.5f, -1.f, 1.f);
    const auto& b = canvas.m_current_stroke->m_brush;

    const auto blend_gate = draw_merge_blend_gate_plan(
        canvas.m_width,
        canvas.m_height,
        canvas.m_layers,
        canvas.m_current_stroke ? canvas.m_current_stroke->m_brush.get() : nullptr);
    const bool use_blend = blend_gate.shader_blend;
    const bool copy_blend_destination = use_blend && !blend_gate.reads_destination_color;

    apply_canvas_capability(depth_test_state(), false);

    execute_canvas_draw_merge_plane_iteration(
        canvas,
        faces,
        canvas.m_layers,
        *b,
        ortho,
        use_blend,
        copy_blend_destination,
        draw_checkerboard);
}

void legacy_canvas_destroy(Canvas& canvas)
{
    for (int i = 0; i < 6; i++)
    {
        canvas.m_tmp[i].destroy();
        canvas.m_tmp_dual[i].destroy();
        canvas.m_tex[i].destroy();
        canvas.m_tex2[i].destroy();
    }
    for (auto& l : canvas.m_layers)
        l->destroy();
    canvas.m_smask.destroy();
    canvas.m_mixer.destroy();
    canvas.m_layers_merge.destroy();
    canvas.m_merge_rtt.destroy();
    canvas.m_merge_tex.destroy();
}

bool legacy_canvas_create(Canvas& canvas, int width, int height)
{
    canvas.m_width = width;
    canvas.m_height = height;
    canvas.m_size = { width, height };
    for (int i = 0; i < 6; i++)
    {
        const auto stroke_format = current_canvas_stroke_internal_format();
        canvas.m_tmp[i].create(width, height, -1, stroke_format);
        canvas.m_tmp_dual[i].create(width, height, -1, stroke_format);
        canvas.m_tex[i].create(width, height, rgba8_internal_format());
        canvas.m_tex2[i].create(width, height, rgba8_internal_format());
    }
#if defined(__GLES__)
    canvas.m_sampler_brush.create();
#else
    canvas.m_sampler_brush.create(pp::renderer::gl::linear_texture_filter(), pp::renderer::gl::clamp_to_border_texture_wrap());
#endif
    canvas.m_sampler.create(pp::renderer::gl::linear_texture_filter());
    canvas.m_sampler_nearest.create(pp::renderer::gl::nearest_texture_filter());
    canvas.m_sampler_brush.set_filter(pp::renderer::gl::linear_mipmap_linear_texture_filter(), pp::renderer::gl::linear_texture_filter());
    canvas.m_sampler_brush.set_border({ 1, 1, 1, 1 });
    canvas.m_sampler_stencil.create(pp::renderer::gl::linear_texture_filter(), pp::renderer::gl::repeat_texture_wrap());
    canvas.m_sampler_mix.create(pp::renderer::gl::nearest_texture_filter(), pp::renderer::gl::repeat_texture_wrap());
    canvas.m_sampler_linear.create();
    canvas.m_plane.create<1>(1, 1);
    canvas.m_plane_brush.create<1>(1, 1);
    canvas.m_brush_shape.create();
    for (auto& l : canvas.m_layers)
        l->create(width, height, "");
    canvas.m_smask.create(width, height, "mask");
    canvas.m_layers_merge.create(width, height, "merge");
    canvas.m_merge_rtt.create(width, height);
    canvas.m_merge_tex.create(width, height);
    canvas.m_unsaved = true;

    canvas.timelapse_reset_encoder();

    return true;
}

void legacy_canvas_clear_context(Canvas& canvas)
{
    LOG("Canvas CLEAR CONTEXT");
    for (auto& layer : canvas.m_layers)
        layer->destroy();
    for (int i = 0; i < 6; i++)
    {
        canvas.m_tmp[i].destroy();
        canvas.m_tex[i].destroy();
        canvas.m_tex2[i].destroy();
    }
}

CameraData legacy_canvas_render_shell_get_camera(const Canvas& canvas)
{
    return legacy_canvas_get_camera(canvas);
}

void legacy_canvas_render_shell_set_camera(Canvas& canvas, const CameraData& camera)
{
    legacy_canvas_set_camera(canvas, camera);
}

} // namespace pp::panopainter

void Canvas::draw_merge_temporary_paint_branch(
    int layer_index,
    int plane_index,
    std::shared_ptr<Layer> layer,
    const Brush& brush,
    const glm::mat4& ortho)
{
    pp::panopainter::legacy_canvas_draw_merge_temporary_paint_branch(
        *this,
        layer_index,
        plane_index,
        std::move(layer),
        brush,
        ortho);
}

void Canvas::draw_merge_branch_orchestration(
    int plane_index,
    int layer_index,
    const std::shared_ptr<Layer>& layer,
    const Brush& brush,
    const glm::mat4& ortho,
    bool use_blend,
    bool copy_blend_destination)
{
    pp::panopainter::legacy_canvas_draw_merge_branch_orchestration(
        *this,
        plane_index,
        layer_index,
        layer,
        brush,
        ortho,
        use_blend,
        copy_blend_destination);
}

void Canvas::draw_merge_final_plane_composite(
    const glm::mat4& ortho,
    bool draw_checkerboard)
{
    pp::panopainter::legacy_canvas_draw_merge_final_plane_composite(*this, ortho, draw_checkerboard);
}

void Canvas::stroke_commit_timelapse()
{
    pp::panopainter::legacy_canvas_stroke_commit_timelapse(*this);
}

void Canvas::draw_merge(bool draw_checkerboard, std::array<bool, 6> faces /*= SIXPLETTE(true)*/)
{
    pp::panopainter::legacy_canvas_draw_merge(*this, draw_checkerboard, faces);
}

void Canvas::destroy()
{
    pp::panopainter::legacy_canvas_destroy(*this);
}

bool Canvas::create(int width, int height)
{
    return pp::panopainter::legacy_canvas_create(*this, width, height);
}

void Canvas::clear_context()
{
    pp::panopainter::legacy_canvas_clear_context(*this);
}
