#include "pch.h" #include "legacy_node_canvas_draw_services.h" #include #include #include #include #include #include "app.h" #include "legacy_canvas_draw_merge_services.h" #include "legacy_preference_storage.h" #include "legacy_ui_gl_dispatch.h" #include "legacy_ui_overlay_services.h" #include "log.h" #include "node_panel_grid.h" #include "node_image_texture.h" #include "paint_renderer/compositor.h" #include "renderer_gl/opengl_capabilities.h" #include "util.h" namespace { void set_active_texture_unit(std::uint32_t unit_index) { pp::legacy::ui_gl::activate_texture_unit(unit_index, "NodeCanvas"); } void unbind_texture_2d() { pp::legacy::ui_gl::unbind_texture_2d("NodeCanvas"); } void apply_node_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, "NodeCanvas"); } pp::renderer::gl::OpenGlViewportRect query_node_canvas_viewport() { return pp::legacy::ui_gl::query_viewport_rect("NodeCanvas"); } std::array query_node_canvas_clear_color() { return pp::legacy::ui_gl::query_clear_color("NodeCanvas"); } void apply_node_canvas_clear_color(std::array color) { pp::legacy::ui_gl::set_clear_color(color, "NodeCanvas"); } void clear_node_canvas_color_buffer(std::array color) { pp::legacy::ui_gl::clear_color_buffer(color, "NodeCanvas"); } void apply_node_canvas_capability(std::uint32_t state, bool enabled) { pp::legacy::ui_gl::set_capability(state, enabled, "NodeCanvas"); } bool query_node_canvas_capability(std::uint32_t state) { return pp::legacy::ui_gl::query_capability(state, "NodeCanvas"); } pp::renderer::RenderDeviceFeatures node_canvas_stroke_composite_features() noexcept { return ShaderManager::render_device_features(); } pp::paint_renderer::CanvasBlendGatePlan node_canvas_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( node_canvas_stroke_composite_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::LegacyCanvasDrawMergeLayerPathExecution make_node_canvas_layer_path_execution( NodeCanvas& node_canvas, size_t layer_index, int plane_index, const glm::mat4& plane_mvp_z, const Brush* brush, bool copy_blend_destination, bool use_nearest_sampler) { auto* layer = node_canvas.m_canvas->m_layers[layer_index].get(); const auto plane_mvp = plane_mvp_z; const auto brush_ptr = brush; const auto smask_active = node_canvas.m_canvas->m_smask_active; auto draw_layer_frame = pp::panopainter::make_legacy_canvas_draw_merge_layer_frame_draw( layer, &node_canvas.m_face_plane, set_active_texture_unit, plane_index, layer->m_opacity); glm::vec2 patt_scale = glm::vec2(brush_ptr->m_pattern_scale); if (brush_ptr->m_pattern_flipx) patt_scale.x *= -1.f; if (brush_ptr->m_pattern_flipy) patt_scale.y *= -1.f; return pp::panopainter::make_legacy_canvas_draw_merge_layer_path_gl_execution( { .temporary_erase = { .mvp = plane_mvp, .texture_slot = 0, .stroke_texture_slot = 1, .mask_texture_slot = 2, .mask_enabled = smask_active, }, .temporary_paint = { .resolution = Canvas::I->m_size, .pattern = { .scale = patt_scale, .invert = static_cast(brush_ptr->m_pattern_invert), .brightness = brush_ptr->m_pattern_brightness, .contrast = brush_ptr->m_pattern_contrast, .depth = brush_ptr->m_pattern_depth, .blend_mode = brush_ptr->m_pattern_blend_mode, .offset = Canvas::I->m_pattern_offset, }, .mvp = plane_mvp, .layer_alpha = 1.0f, .alpha_lock = layer->m_alpha_locked, .mask_enabled = smask_active, .use_fragcoord = false, .blend_mode = brush_ptr->m_blend_mode, .use_dual = brush_ptr->m_dual_enabled, .dual_blend_mode = brush_ptr->m_dual_blend_mode, .dual_alpha = brush_ptr->m_dual_opacity, .use_pattern = brush_ptr->m_pattern_enabled && !brush_ptr->m_pattern_eachsample, }, .layer_texture = { .mvp = plane_mvp, .texture_slot = 0, .alpha = 1.f, .highlight = layer->m_hightlight, }, .blend = { .shader = { .mvp = glm::ortho(-1, 1, -1, 1), .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, }, .use_nearest_sampler = use_nearest_sampler, .use_dual_texture = brush_ptr->m_dual_enabled, }, [&node_canvas] { node_canvas.m_blender_rtt.bindFramebuffer(); }, [&node_canvas] { node_canvas.m_blender_rtt.clear(); }, [&node_canvas] { node_canvas.m_blender_rtt.unbindFramebuffer(); }, [&node_canvas](int unit) { node_canvas.m_sampler.bind(unit); }, [&node_canvas](int unit) { node_canvas.m_sampler_nearest.bind(unit); }, [&node_canvas](int unit) { node_canvas.m_sampler_stencil.bind(unit); }, [](int unit) { set_active_texture_unit(unit); }, [&node_canvas, plane_index] { node_canvas.m_canvas->m_tmp[plane_index].bindTexture(); }, [&node_canvas, plane_index] { node_canvas.m_canvas->m_tmp[plane_index].unbindTexture(); }, [&node_canvas, plane_index] { node_canvas.m_canvas->m_smask.rtt(plane_index).bindTexture(); }, [&node_canvas, plane_index] { node_canvas.m_canvas->m_smask.rtt(plane_index).unbindTexture(); }, [&node_canvas, plane_index] { node_canvas.m_canvas->m_tmp_dual[plane_index].bindTexture(); }, [&node_canvas, plane_index] { node_canvas.m_canvas->m_tmp_dual[plane_index].unbindTexture(); }, [brush_ptr] { brush_ptr->m_pattern_texture ? brush_ptr->m_pattern_texture->bind() : unbind_texture_2d(); }, [&node_canvas] { node_canvas.m_face_plane.draw_fill(); }, [&node_canvas] { node_canvas.m_blender_rtt.bindTexture(); }, [&node_canvas] { node_canvas.m_blender_rtt.unbindTexture(); }, [&node_canvas] { node_canvas.m_blender_bg.bind(); }, [&node_canvas] { node_canvas.m_blender_bg.unbind(); }, [&node_canvas] { copy_framebuffer_to_texture_2d( 0, 0, 0, 0, node_canvas.m_blender_bg.size().x, node_canvas.m_blender_bg.size().y); }, #ifdef _DEBUG [&node_canvas, layer_index, plane_index, plane_mvp] { auto bb = node_canvas.m_canvas->m_layers[layer_index]->box(plane_index) / (float)node_canvas.m_canvas->m_layers[layer_index]->w; glm::vec2 bbmin = xy(bb); glm::vec2 bbsz = zw(bb) - xy(bb); pp::panopainter::configure_legacy_ui_color_shader( plane_mvp * glm::translate(glm::vec3(bbmin * 2.f, 0)) * glm::translate(glm::vec3(-1, -1, 0)) * glm::scale(glm::vec3(bbsz, 1)) * glm::translate(glm::vec3(1, 1, 0)), { 1, 0, 0, 1 }); node_canvas.m_face_plane.draw_stroke(); }, #else [] { }, #endif draw_layer_frame); } void execute_node_canvas_draw_unmerged_pass( NodeCanvas& node_canvas, const glm::mat4& proj, const glm::mat4& camera, const glm::ivec4& c, float yaw, float pitch, float roll) { const auto blend_gate = node_canvas_blend_gate_plan( node_canvas.m_cache_rtt.getWidth(), node_canvas.m_cache_rtt.getHeight(), node_canvas.m_canvas->m_layers, node_canvas.m_canvas->m_current_stroke ? node_canvas.m_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; const auto layer_orientation = glm::eulerAngleYXZ(yaw, pitch, roll); pp::panopainter::execute_legacy_canvas_draw_node_canvas_unmerged_pass( node_canvas, use_blend, copy_blend_destination, proj, camera, layer_orientation, c, [&](int x, int y, int width, int height) { apply_node_canvas_viewport(x, y, width, height); }, [&](auto state, bool enabled) { apply_node_canvas_capability(state, enabled); }, [&] { node_canvas.m_sampler.bind(0); set_active_texture_unit(0); }, [&](size_t layer_index, int plane_index, const glm::mat4& plane_mvp_z, const Brush* brush, bool copy_blend_destination, bool use_nearest_sampler) { return make_node_canvas_layer_path_execution( node_canvas, layer_index, plane_index, plane_mvp_z, brush, copy_blend_destination, use_nearest_sampler); }, [&](const char* message) { LOG("NodeCanvas onion frame range failed: %s", message); }); } void execute_node_canvas_draw_merged_pass( NodeCanvas& node_canvas, const glm::mat4& proj, const glm::mat4& camera) { pp::panopainter::execute_legacy_canvas_draw_merged_pass( node_canvas, proj, camera, [&](auto state, bool enabled) { apply_node_canvas_capability(state, enabled); }, [](int unit) { set_active_texture_unit(unit); }, [&] { node_canvas.m_face_plane.draw_fill(); }); } void execute_node_canvas_draw_merge_tail( NodeCanvas& node_canvas, const glm::mat4& ortho_proj, const glm::mat4& proj, const glm::mat4& camera, const glm::ivec4& c) { apply_node_canvas_capability(pp::renderer::gl::depth_test_state(), false); pp::panopainter::execute_legacy_canvas_draw_merge_post_draw_callbacks( node_canvas.m_canvas->m_smask_active, node_canvas.m_canvas->m_current_mode == kCanvasMode::Copy || node_canvas.m_canvas->m_current_mode == kCanvasMode::Cut, node_canvas.m_canvas->m_smask_mode, node_canvas.m_canvas->m_current_mode != kCanvasMode::Grid, [&] { node_canvas.m_canvas->modes[(int)kCanvasMode::MaskFree][0]->on_Draw(ortho_proj, proj, camera); }, [&] { node_canvas.m_canvas->modes[(int)kCanvasMode::MaskLine][0]->on_Draw(ortho_proj, proj, camera); }, [&] { pp::panopainter::execute_legacy_canvas_draw_merge_smask_faces( pp::panopainter::LegacyCanvasDrawMergeTextureMaskUniforms { .texture_slot = 0, .pattern_offset = node_canvas.m_outline_pan, }, proj, camera, node_canvas.m_canvas->m_layers.size() + 500.f, std::to_array(node_canvas.m_canvas->m_plane_transform), { .set_active_texture_unit = [&] { set_active_texture_unit(0); }, .enable_blend = [&] { apply_node_canvas_capability(pp::renderer::gl::blend_state(), true); }, .bind_face_texture = [&](int plane_index) { node_canvas.m_canvas->m_smask.rtt(plane_index).bindTexture(); }, .draw_face = [&] { node_canvas.m_face_plane.draw_fill(); }, .unbind_face_texture = [&](int plane_index) { node_canvas.m_canvas->m_smask.rtt(plane_index).unbindTexture(); }, }); }, pp::panopainter::make_legacy_canvas_draw_merge_grid_modes_draw( &Canvas::modes[(int)kCanvasMode::Grid], ortho_proj, proj, camera), pp::panopainter::make_legacy_canvas_draw_merge_heightmap_draw(App::I->grid.get(), proj, camera), pp::panopainter::make_legacy_canvas_draw_merge_current_modes_draw( node_canvas.m_canvas->m_mode, ortho_proj, proj, camera)); if (node_canvas.m_density != 1.f) { pp::panopainter::execute_legacy_canvas_draw_merge_display_resolve( pp::panopainter::LegacyCanvasDrawMergeDisplayResolveUniforms { .texture = { .mvp = glm::ortho(-1, 1, -1, 1), .texture_slot = 0, }, }, { .unbind_resolve_framebuffer = [&] { node_canvas.m_rtt.unbindFramebuffer(); }, .clear_color_buffer = [&] { clear_node_canvas_color_buffer({ 1.f, 1.f, 1.f, 0.f }); }, .apply_viewport = [&] { apply_node_canvas_viewport(c.x + App::I->off_x, c.y + App::I->off_y, c.z, c.w); }, .bind_sampler = [&] { node_canvas.m_sampler_nearest.bind(0); }, .bind_resolve_texture = [&] { set_active_texture_unit(0); node_canvas.m_rtt.bindTexture(); }, .draw = [&] { node_canvas.m_face_plane.draw_fill(); }, .unbind_resolve_texture = [&] { node_canvas.m_rtt.unbindTexture(); }, }); } } } // namespace namespace pp::panopainter { void init_legacy_node_canvas_shell(NodeCanvas& node_canvas) { const auto preferences = read_legacy_canvas_preferences(); node_canvas.m_density = preferences.viewport_density; node_canvas.m_cursor_visibility = (NodeCanvas::kCursorVisibility)preferences.cursor_mode; node_canvas.m_mouse_ignore = false; node_canvas.m_canvas = std::make_unique(); const int canvas_resolution = App::I->default_canvas_resolution(); node_canvas.m_canvas->create(canvas_resolution, canvas_resolution); node_canvas.m_canvas->m_unsaved = false; node_canvas.m_canvas->m_node = &node_canvas; node_canvas.m_sampler.create(); //node_canvas.m_sampler.set_filter(pp::renderer::gl::linear_texture_filter(), pp::renderer::gl::nearest_texture_filter()); node_canvas.m_sampler_nearest.create(pp::renderer::gl::nearest_texture_filter()); node_canvas.m_sampler_linear.create(pp::renderer::gl::linear_texture_filter()); node_canvas.m_sampler_stencil.create( pp::renderer::gl::linear_texture_filter(), pp::renderer::gl::repeat_texture_wrap()); node_canvas.m_face_plane.create<1>(2, 2); node_canvas.m_line.create(); CanvasMode::node = &node_canvas; for (int i = 0; i < (int)kCanvasMode::COUNT; i++) for (auto m : Canvas::modes[i]) m->init(); node_canvas.m_grid.create(1, 1, node_canvas.m_grid_divs); } void draw_legacy_node_canvas_shell(NodeCanvas& node_canvas) { // sanity checks float zoom = node_canvas.root()->m_zoom; if (zoom == 0.f) zoom = 1.f; auto box = node_canvas.m_clip * zoom; if (box.z == 0 || box.w == 0) return; const auto vp = query_node_canvas_viewport(); const auto cc = query_node_canvas_clear_color(); const auto blend = query_node_canvas_capability(pp::renderer::gl::blend_state()); const auto depth = query_node_canvas_capability(pp::renderer::gl::depth_test_state()); const auto scissor = query_node_canvas_capability(pp::renderer::gl::scissor_test_state()); apply_node_canvas_capability(pp::renderer::gl::scissor_test_state(), false); glm::ivec4 c = (glm::ivec4)glm::vec4(box.x, (int)(vp.height - box.y - box.w), box.z, box.w); //m_canvas->m_cam_rot = m_pan * 0.003f; glm::mat4 ortho_proj = glm::ortho(0.f, box.z, 0.f, box.w, -1000.f, 1000.f); glm::mat4 proj = glm::perspective(glm::radians(node_canvas.m_canvas->m_cam_fov), box.z / box.w, 0.001f, 1000.f); glm::mat4 camera = glm::translate(node_canvas.m_canvas->m_cam_pos) * node_canvas.m_canvas->m_cam_rot; float pitch = 0; if (auto slider = node_canvas.root()->find("pitch-slider")) pitch = (slider->get_value() - 0.5) * glm::half_pi(); float yaw = 0; if (auto slider = node_canvas.root()->find("yaw-slider")) yaw = (slider->get_value() - 0.5) * glm::half_pi(); float roll = 0; if (auto slider = node_canvas.root()->find("roll-slider")) roll = (slider->get_value() - 0.5) * glm::half_pi(); prepare_legacy_node_canvas_draw_setup(node_canvas, box, c, proj, camera); // NOTE: draw_merge has been disabled for worst performance bool draw_merged = !(node_canvas.m_canvas->m_current_mode == kCanvasMode::Camera); draw_merged = false; execute_legacy_canvas_draw_node_canvas_shell( node_canvas.m_density != 1.f, draw_merged, [&] { node_canvas.m_rtt.bindFramebuffer(); clear_node_canvas_color_buffer({ 1.f, 1.f, 0.f, 0.f }); apply_node_canvas_viewport(0, 0, node_canvas.m_rtt.getWidth(), node_canvas.m_rtt.getHeight()); }, [&] { clear_node_canvas_color_buffer({ 1.f, 1.f, 1.f, 0.f }); apply_node_canvas_viewport(c.x + App::I->off_x, c.y + App::I->off_y, c.z, c.w); }, [&] { execute_node_canvas_draw_merged_pass(node_canvas, proj, camera); }, [&] { execute_node_canvas_draw_unmerged_pass(node_canvas, proj, camera, c, yaw, pitch, roll); }); execute_node_canvas_draw_merge_tail(node_canvas, ortho_proj, proj, camera, c); scissor ? apply_node_canvas_capability(pp::renderer::gl::scissor_test_state(), true) : apply_node_canvas_capability(pp::renderer::gl::scissor_test_state(), false); blend ? apply_node_canvas_capability(pp::renderer::gl::blend_state(), true) : apply_node_canvas_capability(pp::renderer::gl::blend_state(), false); depth ? apply_node_canvas_capability(pp::renderer::gl::depth_test_state(), true) : apply_node_canvas_capability(pp::renderer::gl::depth_test_state(), false); apply_node_canvas_viewport(vp.x, vp.y, vp.width, vp.height); apply_node_canvas_clear_color(cc); } } // namespace pp::panopainter