#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 #include #include #include #include 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(pp::renderer::gl::rgba32f_internal_format()); if (renderer_features.float16_render_targets) return static_cast(pp::renderer::gl::rgba16f_internal_format()); return static_cast(pp::renderer::gl::rgba8_internal_format()); } GLint rgba8_internal_format() { return static_cast(pp::renderer::gl::rgba8_internal_format()); } GLenum depth_test_state() { return static_cast(pp::renderer::gl::depth_test_state()); } GLenum blend_state() { return static_cast(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>& layers, const Brush* brush) noexcept { std::vector 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::max(width, 0)), .height = static_cast(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, 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, 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, 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>& 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& faces, const std::vector>& 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, 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, 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(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, 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, 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(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 faces) { assert(App::I->is_render_thread()); apply_canvas_viewport(0, 0, canvas.m_width, canvas.m_height); auto ortho = glm::ortho(-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, 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, 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 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); }