summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/gltf/editor/editor_scene_exporter_gltf_plugin.h2
-rw-r--r--modules/gridmap/editor/grid_map_editor_plugin.h3
-rw-r--r--modules/lightmapper_rd/register_types.cpp1
-rw-r--r--modules/multiplayer/editor/editor_network_profiler.cpp197
-rw-r--r--modules/multiplayer/editor/editor_network_profiler.h76
-rw-r--r--modules/multiplayer/editor/multiplayer_editor_plugin.cpp140
-rw-r--r--modules/multiplayer/editor/multiplayer_editor_plugin.h81
-rw-r--r--modules/multiplayer/editor/replication_editor.cpp (renamed from modules/multiplayer/editor/replication_editor_plugin.cpp)69
-rw-r--r--modules/multiplayer/editor/replication_editor.h (renamed from modules/multiplayer/editor/replication_editor_plugin.h)43
-rw-r--r--modules/multiplayer/multiplayer_debugger.cpp194
-rw-r--r--modules/multiplayer/multiplayer_debugger.h95
-rw-r--r--modules/multiplayer/register_types.cpp9
-rw-r--r--modules/navigation/editor/navigation_mesh_editor_plugin.h2
-rw-r--r--modules/noise/editor/noise_editor_plugin.cpp2
-rw-r--r--modules/openxr/extensions/openxr_opengl_extension.cpp13
-rw-r--r--modules/openxr/openxr_interface.cpp2
-rw-r--r--modules/text_server_adv/SCsub3
-rw-r--r--modules/text_server_adv/gdextension_build/SConstruct74
-rw-r--r--modules/text_server_adv/text_server_adv.cpp14
-rw-r--r--modules/text_server_adv/text_server_adv.h3
-rw-r--r--modules/text_server_adv/thorvg_bounds_iterator.cpp70
-rw-r--r--modules/text_server_adv/thorvg_bounds_iterator.h58
-rw-r--r--modules/text_server_adv/thorvg_svg_in_ot.cpp320
-rw-r--r--modules/text_server_adv/thorvg_svg_in_ot.h86
-rw-r--r--modules/text_server_fb/SCsub3
-rw-r--r--modules/text_server_fb/gdextension_build/SConstruct74
-rw-r--r--modules/text_server_fb/text_server_fb.cpp14
-rw-r--r--modules/text_server_fb/text_server_fb.h3
-rw-r--r--modules/text_server_fb/thorvg_bounds_iterator.cpp70
-rw-r--r--modules/text_server_fb/thorvg_bounds_iterator.h58
-rw-r--r--modules/text_server_fb/thorvg_svg_in_ot.cpp320
-rw-r--r--modules/text_server_fb/thorvg_svg_in_ot.h86
-rw-r--r--modules/zip/zip_packer.cpp12
33 files changed, 2075 insertions, 122 deletions
diff --git a/modules/gltf/editor/editor_scene_exporter_gltf_plugin.h b/modules/gltf/editor/editor_scene_exporter_gltf_plugin.h
index 5af46bc752..66fd27b449 100644
--- a/modules/gltf/editor/editor_scene_exporter_gltf_plugin.h
+++ b/modules/gltf/editor/editor_scene_exporter_gltf_plugin.h
@@ -36,6 +36,8 @@
#include "editor/editor_plugin.h"
#include "editor_scene_importer_gltf.h"
+class EditorFileDialog;
+
class SceneExporterGLTFPlugin : public EditorPlugin {
GDCLASS(SceneExporterGLTFPlugin, EditorPlugin);
diff --git a/modules/gridmap/editor/grid_map_editor_plugin.h b/modules/gridmap/editor/grid_map_editor_plugin.h
index 6fd38d9445..91f14690ca 100644
--- a/modules/gridmap/editor/grid_map_editor_plugin.h
+++ b/modules/gridmap/editor/grid_map_editor_plugin.h
@@ -35,11 +35,14 @@
#include "../grid_map.h"
#include "editor/editor_plugin.h"
+#include "scene/gui/box_container.h"
#include "scene/gui/item_list.h"
#include "scene/gui/slider.h"
#include "scene/gui/spin_box.h"
+class ConfirmationDialog;
class EditorUndoRedoManager;
+class MenuButton;
class Node3DEditorPlugin;
class GridMapEditor : public VBoxContainer {
diff --git a/modules/lightmapper_rd/register_types.cpp b/modules/lightmapper_rd/register_types.cpp
index 0e0330c1a1..ed223e1faa 100644
--- a/modules/lightmapper_rd/register_types.cpp
+++ b/modules/lightmapper_rd/register_types.cpp
@@ -57,6 +57,7 @@ void initialize_lightmapper_rd_module(ModuleInitializationLevel p_level) {
GLOBAL_DEF("rendering/lightmapping/bake_quality/high_quality_probe_ray_count", 512);
GLOBAL_DEF("rendering/lightmapping/bake_quality/ultra_quality_probe_ray_count", 2048);
GLOBAL_DEF("rendering/lightmapping/bake_performance/max_rays_per_probe_pass", 64);
+ GLOBAL_DEF("rendering/lightmapping/primitive_meshes/texel_size", 0.2);
#ifndef _3D_DISABLED
GDREGISTER_CLASS(LightmapperRD);
Lightmapper::create_gpu = create_lightmapper_rd;
diff --git a/modules/multiplayer/editor/editor_network_profiler.cpp b/modules/multiplayer/editor/editor_network_profiler.cpp
new file mode 100644
index 0000000000..a7e5b80b66
--- /dev/null
+++ b/modules/multiplayer/editor/editor_network_profiler.cpp
@@ -0,0 +1,197 @@
+/*************************************************************************/
+/* editor_network_profiler.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "editor_network_profiler.h"
+
+#include "core/os/os.h"
+#include "editor/editor_scale.h"
+#include "editor/editor_settings.h"
+
+void EditorNetworkProfiler::_bind_methods() {
+ ADD_SIGNAL(MethodInfo("enable_profiling", PropertyInfo(Variant::BOOL, "enable")));
+}
+
+void EditorNetworkProfiler::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_ENTER_TREE:
+ case NOTIFICATION_THEME_CHANGED: {
+ activate->set_icon(get_theme_icon(SNAME("Play"), SNAME("EditorIcons")));
+ clear_button->set_icon(get_theme_icon(SNAME("Clear"), SNAME("EditorIcons")));
+ incoming_bandwidth_text->set_right_icon(get_theme_icon(SNAME("ArrowDown"), SNAME("EditorIcons")));
+ outgoing_bandwidth_text->set_right_icon(get_theme_icon(SNAME("ArrowUp"), SNAME("EditorIcons")));
+
+ // This needs to be done here to set the faded color when the profiler is first opened
+ incoming_bandwidth_text->add_theme_color_override("font_uneditable_color", get_theme_color(SNAME("font_color"), SNAME("Editor")) * Color(1, 1, 1, 0.5));
+ outgoing_bandwidth_text->add_theme_color_override("font_uneditable_color", get_theme_color(SNAME("font_color"), SNAME("Editor")) * Color(1, 1, 1, 0.5));
+ } break;
+ }
+}
+
+void EditorNetworkProfiler::_update_frame() {
+ counters_display->clear();
+
+ TreeItem *root = counters_display->create_item();
+
+ for (const KeyValue<ObjectID, RPCNodeInfo> &E : nodes_data) {
+ TreeItem *node = counters_display->create_item(root);
+
+ for (int j = 0; j < counters_display->get_columns(); ++j) {
+ node->set_text_alignment(j, j > 0 ? HORIZONTAL_ALIGNMENT_RIGHT : HORIZONTAL_ALIGNMENT_LEFT);
+ }
+
+ node->set_text(0, E.value.node_path);
+ node->set_text(1, E.value.incoming_rpc == 0 ? "-" : itos(E.value.incoming_rpc));
+ node->set_text(2, E.value.outgoing_rpc == 0 ? "-" : itos(E.value.outgoing_rpc));
+ }
+}
+
+void EditorNetworkProfiler::_activate_pressed() {
+ if (activate->is_pressed()) {
+ activate->set_icon(get_theme_icon(SNAME("Stop"), SNAME("EditorIcons")));
+ activate->set_text(TTR("Stop"));
+ } else {
+ activate->set_icon(get_theme_icon(SNAME("Play"), SNAME("EditorIcons")));
+ activate->set_text(TTR("Start"));
+ }
+ emit_signal(SNAME("enable_profiling"), activate->is_pressed());
+}
+
+void EditorNetworkProfiler::_clear_pressed() {
+ nodes_data.clear();
+ set_bandwidth(0, 0);
+ if (frame_delay->is_stopped()) {
+ frame_delay->set_wait_time(0.1);
+ frame_delay->start();
+ }
+}
+
+void EditorNetworkProfiler::add_node_frame_data(const RPCNodeInfo p_frame) {
+ if (!nodes_data.has(p_frame.node)) {
+ nodes_data.insert(p_frame.node, p_frame);
+ } else {
+ nodes_data[p_frame.node].incoming_rpc += p_frame.incoming_rpc;
+ nodes_data[p_frame.node].outgoing_rpc += p_frame.outgoing_rpc;
+ }
+
+ if (frame_delay->is_stopped()) {
+ frame_delay->set_wait_time(0.1);
+ frame_delay->start();
+ }
+}
+
+void EditorNetworkProfiler::set_bandwidth(int p_incoming, int p_outgoing) {
+ incoming_bandwidth_text->set_text(vformat(TTR("%s/s"), String::humanize_size(p_incoming)));
+ outgoing_bandwidth_text->set_text(vformat(TTR("%s/s"), String::humanize_size(p_outgoing)));
+
+ // Make labels more prominent when the bandwidth is greater than 0 to attract user attention
+ incoming_bandwidth_text->add_theme_color_override(
+ "font_uneditable_color",
+ get_theme_color(SNAME("font_color"), SNAME("Editor")) * Color(1, 1, 1, p_incoming > 0 ? 1 : 0.5));
+ outgoing_bandwidth_text->add_theme_color_override(
+ "font_uneditable_color",
+ get_theme_color(SNAME("font_color"), SNAME("Editor")) * Color(1, 1, 1, p_outgoing > 0 ? 1 : 0.5));
+}
+
+bool EditorNetworkProfiler::is_profiling() {
+ return activate->is_pressed();
+}
+
+EditorNetworkProfiler::EditorNetworkProfiler() {
+ HBoxContainer *hb = memnew(HBoxContainer);
+ hb->add_theme_constant_override("separation", 8 * EDSCALE);
+ add_child(hb);
+
+ activate = memnew(Button);
+ activate->set_toggle_mode(true);
+ activate->set_text(TTR("Start"));
+ activate->connect("pressed", callable_mp(this, &EditorNetworkProfiler::_activate_pressed));
+ hb->add_child(activate);
+
+ clear_button = memnew(Button);
+ clear_button->set_text(TTR("Clear"));
+ clear_button->connect("pressed", callable_mp(this, &EditorNetworkProfiler::_clear_pressed));
+ hb->add_child(clear_button);
+
+ hb->add_spacer();
+
+ Label *lb = memnew(Label);
+ lb->set_text(TTR("Down"));
+ hb->add_child(lb);
+
+ incoming_bandwidth_text = memnew(LineEdit);
+ incoming_bandwidth_text->set_editable(false);
+ incoming_bandwidth_text->set_custom_minimum_size(Size2(120, 0) * EDSCALE);
+ incoming_bandwidth_text->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT);
+ hb->add_child(incoming_bandwidth_text);
+
+ Control *down_up_spacer = memnew(Control);
+ down_up_spacer->set_custom_minimum_size(Size2(30, 0) * EDSCALE);
+ hb->add_child(down_up_spacer);
+
+ lb = memnew(Label);
+ lb->set_text(TTR("Up"));
+ hb->add_child(lb);
+
+ outgoing_bandwidth_text = memnew(LineEdit);
+ outgoing_bandwidth_text->set_editable(false);
+ outgoing_bandwidth_text->set_custom_minimum_size(Size2(120, 0) * EDSCALE);
+ outgoing_bandwidth_text->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_RIGHT);
+ hb->add_child(outgoing_bandwidth_text);
+
+ // Set initial texts in the incoming/outgoing bandwidth labels
+ set_bandwidth(0, 0);
+
+ counters_display = memnew(Tree);
+ counters_display->set_custom_minimum_size(Size2(300, 0) * EDSCALE);
+ counters_display->set_v_size_flags(SIZE_EXPAND_FILL);
+ counters_display->set_hide_folding(true);
+ counters_display->set_hide_root(true);
+ counters_display->set_columns(3);
+ counters_display->set_column_titles_visible(true);
+ counters_display->set_column_title(0, TTR("Node"));
+ counters_display->set_column_expand(0, true);
+ counters_display->set_column_clip_content(0, true);
+ counters_display->set_column_custom_minimum_width(0, 60 * EDSCALE);
+ counters_display->set_column_title(1, TTR("Incoming RPC"));
+ counters_display->set_column_expand(1, false);
+ counters_display->set_column_clip_content(1, true);
+ counters_display->set_column_custom_minimum_width(1, 120 * EDSCALE);
+ counters_display->set_column_title(2, TTR("Outgoing RPC"));
+ counters_display->set_column_expand(2, false);
+ counters_display->set_column_clip_content(2, true);
+ counters_display->set_column_custom_minimum_width(2, 120 * EDSCALE);
+ add_child(counters_display);
+
+ frame_delay = memnew(Timer);
+ frame_delay->set_wait_time(0.1);
+ frame_delay->set_one_shot(true);
+ add_child(frame_delay);
+ frame_delay->connect("timeout", callable_mp(this, &EditorNetworkProfiler::_update_frame));
+}
diff --git a/modules/multiplayer/editor/editor_network_profiler.h b/modules/multiplayer/editor/editor_network_profiler.h
new file mode 100644
index 0000000000..98d12e3c0a
--- /dev/null
+++ b/modules/multiplayer/editor/editor_network_profiler.h
@@ -0,0 +1,76 @@
+/*************************************************************************/
+/* editor_network_profiler.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef EDITOR_NETWORK_PROFILER_H
+#define EDITOR_NETWORK_PROFILER_H
+
+#include "scene/debugger/scene_debugger.h"
+#include "scene/gui/box_container.h"
+#include "scene/gui/button.h"
+#include "scene/gui/label.h"
+#include "scene/gui/split_container.h"
+#include "scene/gui/tree.h"
+
+#include "../multiplayer_debugger.h"
+
+class EditorNetworkProfiler : public VBoxContainer {
+ GDCLASS(EditorNetworkProfiler, VBoxContainer)
+
+private:
+ using RPCNodeInfo = MultiplayerDebugger::RPCNodeInfo;
+
+ Button *activate = nullptr;
+ Button *clear_button = nullptr;
+ Tree *counters_display = nullptr;
+ LineEdit *incoming_bandwidth_text = nullptr;
+ LineEdit *outgoing_bandwidth_text = nullptr;
+
+ Timer *frame_delay = nullptr;
+
+ HashMap<ObjectID, RPCNodeInfo> nodes_data;
+
+ void _update_frame();
+
+ void _activate_pressed();
+ void _clear_pressed();
+
+protected:
+ void _notification(int p_what);
+ static void _bind_methods();
+
+public:
+ void add_node_frame_data(const RPCNodeInfo p_frame);
+ void set_bandwidth(int p_incoming, int p_outgoing);
+ bool is_profiling();
+
+ EditorNetworkProfiler();
+};
+
+#endif // EDITOR_NETWORK_PROFILER_H
diff --git a/modules/multiplayer/editor/multiplayer_editor_plugin.cpp b/modules/multiplayer/editor/multiplayer_editor_plugin.cpp
new file mode 100644
index 0000000000..00b1537827
--- /dev/null
+++ b/modules/multiplayer/editor/multiplayer_editor_plugin.cpp
@@ -0,0 +1,140 @@
+/*************************************************************************/
+/* multiplayer_editor_plugin.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "multiplayer_editor_plugin.h"
+
+#include "../multiplayer_synchronizer.h"
+#include "editor_network_profiler.h"
+#include "replication_editor.h"
+
+#include "editor/editor_node.h"
+
+bool MultiplayerEditorDebugger::has_capture(const String &p_capture) const {
+ return p_capture == "multiplayer";
+}
+
+bool MultiplayerEditorDebugger::capture(const String &p_message, const Array &p_data, int p_session) {
+ ERR_FAIL_COND_V(!profilers.has(p_session), false);
+ EditorNetworkProfiler *profiler = profilers[p_session];
+ if (p_message == "multiplayer:rpc") {
+ MultiplayerDebugger::RPCFrame frame;
+ frame.deserialize(p_data);
+ for (int i = 0; i < frame.infos.size(); i++) {
+ profiler->add_node_frame_data(frame.infos[i]);
+ }
+ return true;
+
+ } else if (p_message == "multiplayer:bandwidth") {
+ ERR_FAIL_COND_V(p_data.size() < 2, false);
+ profiler->set_bandwidth(p_data[0], p_data[1]);
+ return true;
+ }
+ return false;
+}
+
+void MultiplayerEditorDebugger::_profiler_activate(bool p_enable, int p_session_id) {
+ Ref<EditorDebuggerSession> session = get_session(p_session_id);
+ ERR_FAIL_COND(session.is_null());
+ session->toggle_profiler("multiplayer", p_enable);
+ session->toggle_profiler("rpc", p_enable);
+}
+
+void MultiplayerEditorDebugger::setup_session(int p_session_id) {
+ Ref<EditorDebuggerSession> session = get_session(p_session_id);
+ ERR_FAIL_COND(session.is_null());
+ EditorNetworkProfiler *profiler = memnew(EditorNetworkProfiler);
+ profiler->connect("enable_profiling", callable_mp(this, &MultiplayerEditorDebugger::_profiler_activate).bind(p_session_id));
+ profiler->set_name(TTR("Network Profiler"));
+ session->add_session_tab(profiler);
+ profilers[p_session_id] = profiler;
+}
+
+MultiplayerEditorPlugin::MultiplayerEditorPlugin() {
+ repl_editor = memnew(ReplicationEditor);
+ button = EditorNode::get_singleton()->add_bottom_panel_item(TTR("Replication"), repl_editor);
+ button->hide();
+ repl_editor->get_pin()->connect("pressed", callable_mp(this, &MultiplayerEditorPlugin::_pinned));
+ debugger.instantiate();
+}
+
+MultiplayerEditorPlugin::~MultiplayerEditorPlugin() {
+}
+
+void MultiplayerEditorPlugin::_notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_ENTER_TREE: {
+ get_tree()->connect("node_removed", callable_mp(this, &MultiplayerEditorPlugin::_node_removed));
+ add_debugger_plugin(debugger);
+ } break;
+ case NOTIFICATION_EXIT_TREE: {
+ remove_debugger_plugin(debugger);
+ }
+ }
+}
+
+void MultiplayerEditorPlugin::_node_removed(Node *p_node) {
+ if (p_node && p_node == repl_editor->get_current()) {
+ repl_editor->edit(nullptr);
+ if (repl_editor->is_visible_in_tree()) {
+ EditorNode::get_singleton()->hide_bottom_panel();
+ }
+ button->hide();
+ repl_editor->get_pin()->set_pressed(false);
+ }
+}
+
+void MultiplayerEditorPlugin::_pinned() {
+ if (!repl_editor->get_pin()->is_pressed()) {
+ if (repl_editor->is_visible_in_tree()) {
+ EditorNode::get_singleton()->hide_bottom_panel();
+ }
+ button->hide();
+ }
+}
+
+void MultiplayerEditorPlugin::edit(Object *p_object) {
+ repl_editor->edit(Object::cast_to<MultiplayerSynchronizer>(p_object));
+}
+
+bool MultiplayerEditorPlugin::handles(Object *p_object) const {
+ return p_object->is_class("MultiplayerSynchronizer");
+}
+
+void MultiplayerEditorPlugin::make_visible(bool p_visible) {
+ if (p_visible) {
+ button->show();
+ EditorNode::get_singleton()->make_bottom_panel_item_visible(repl_editor);
+ } else if (!repl_editor->get_pin()->is_pressed()) {
+ if (repl_editor->is_visible_in_tree()) {
+ EditorNode::get_singleton()->hide_bottom_panel();
+ }
+ button->hide();
+ }
+}
diff --git a/modules/multiplayer/editor/multiplayer_editor_plugin.h b/modules/multiplayer/editor/multiplayer_editor_plugin.h
new file mode 100644
index 0000000000..6d1514cdb1
--- /dev/null
+++ b/modules/multiplayer/editor/multiplayer_editor_plugin.h
@@ -0,0 +1,81 @@
+/*************************************************************************/
+/* multiplayer_editor_plugin.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef MULTIPLAYER_EDITOR_PLUGIN_H
+#define MULTIPLAYER_EDITOR_PLUGIN_H
+
+#include "editor/editor_plugin.h"
+
+#include "editor/plugins/editor_debugger_plugin.h"
+
+class EditorNetworkProfiler;
+class MultiplayerEditorDebugger : public EditorDebuggerPlugin {
+ GDCLASS(MultiplayerEditorDebugger, EditorDebuggerPlugin);
+
+private:
+ HashMap<int, EditorNetworkProfiler *> profilers;
+
+ void _profiler_activate(bool p_enable, int p_session_id);
+
+public:
+ virtual bool has_capture(const String &p_capture) const override;
+ virtual bool capture(const String &p_message, const Array &p_data, int p_index) override;
+ virtual void setup_session(int p_session_id) override;
+
+ MultiplayerEditorDebugger() {}
+};
+
+class ReplicationEditor;
+
+class MultiplayerEditorPlugin : public EditorPlugin {
+ GDCLASS(MultiplayerEditorPlugin, EditorPlugin);
+
+private:
+ Button *button = nullptr;
+ ReplicationEditor *repl_editor = nullptr;
+ Ref<MultiplayerEditorDebugger> debugger;
+
+ void _node_removed(Node *p_node);
+
+ void _pinned();
+
+protected:
+ void _notification(int p_what);
+
+public:
+ virtual void edit(Object *p_object) override;
+ virtual bool handles(Object *p_object) const override;
+ virtual void make_visible(bool p_visible) override;
+
+ MultiplayerEditorPlugin();
+ ~MultiplayerEditorPlugin();
+};
+
+#endif // MULTIPLAYER_EDITOR_PLUGIN_H
diff --git a/modules/multiplayer/editor/replication_editor_plugin.cpp b/modules/multiplayer/editor/replication_editor.cpp
index aee5f5b483..4ba9cd14f0 100644
--- a/modules/multiplayer/editor/replication_editor_plugin.cpp
+++ b/modules/multiplayer/editor/replication_editor.cpp
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* replication_editor_plugin.cpp */
+/* replication_editor.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,15 +28,17 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#include "replication_editor_plugin.h"
+#include "replication_editor.h"
+
+#include "../multiplayer_synchronizer.h"
#include "editor/editor_node.h"
#include "editor/editor_scale.h"
#include "editor/editor_settings.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/inspector_dock.h"
+#include "editor/property_selector.h"
#include "editor/scene_tree_editor.h"
-#include "modules/multiplayer/multiplayer_synchronizer.h"
#include "scene/gui/dialogs.h"
#include "scene/gui/separator.h"
#include "scene/gui/tree.h"
@@ -140,7 +142,7 @@ void ReplicationEditor::_add_sync_property(String p_path) {
return;
}
- Ref<EditorUndoRedoManager> undo_redo = EditorNode::get_singleton()->get_undo_redo();
+ Ref<EditorUndoRedoManager> &undo_redo = EditorNode::get_singleton()->get_undo_redo();
undo_redo->create_action(TTR("Add property to synchronizer"));
if (config.is_null()) {
@@ -492,62 +494,3 @@ void ReplicationEditor::_add_property(const NodePath &p_property, bool p_spawn,
item->set_checked(2, p_sync);
item->set_editable(2, true);
}
-
-/// ReplicationEditorPlugin
-ReplicationEditorPlugin::ReplicationEditorPlugin() {
- repl_editor = memnew(ReplicationEditor);
- button = EditorNode::get_singleton()->add_bottom_panel_item(TTR("Replication"), repl_editor);
- button->hide();
- repl_editor->get_pin()->connect("pressed", callable_mp(this, &ReplicationEditorPlugin::_pinned));
-}
-
-ReplicationEditorPlugin::~ReplicationEditorPlugin() {
-}
-
-void ReplicationEditorPlugin::_notification(int p_what) {
- switch (p_what) {
- case NOTIFICATION_ENTER_TREE: {
- get_tree()->connect("node_removed", callable_mp(this, &ReplicationEditorPlugin::_node_removed));
- } break;
- }
-}
-
-void ReplicationEditorPlugin::_node_removed(Node *p_node) {
- if (p_node && p_node == repl_editor->get_current()) {
- repl_editor->edit(nullptr);
- if (repl_editor->is_visible_in_tree()) {
- EditorNode::get_singleton()->hide_bottom_panel();
- }
- button->hide();
- repl_editor->get_pin()->set_pressed(false);
- }
-}
-
-void ReplicationEditorPlugin::_pinned() {
- if (!repl_editor->get_pin()->is_pressed()) {
- if (repl_editor->is_visible_in_tree()) {
- EditorNode::get_singleton()->hide_bottom_panel();
- }
- button->hide();
- }
-}
-
-void ReplicationEditorPlugin::edit(Object *p_object) {
- repl_editor->edit(Object::cast_to<MultiplayerSynchronizer>(p_object));
-}
-
-bool ReplicationEditorPlugin::handles(Object *p_object) const {
- return p_object->is_class("MultiplayerSynchronizer");
-}
-
-void ReplicationEditorPlugin::make_visible(bool p_visible) {
- if (p_visible) {
- button->show();
- EditorNode::get_singleton()->make_bottom_panel_item_visible(repl_editor);
- } else if (!repl_editor->get_pin()->is_pressed()) {
- if (repl_editor->is_visible_in_tree()) {
- EditorNode::get_singleton()->hide_bottom_panel();
- }
- button->hide();
- }
-}
diff --git a/modules/multiplayer/editor/replication_editor_plugin.h b/modules/multiplayer/editor/replication_editor.h
index e60e49cc25..8a48e8dbe7 100644
--- a/modules/multiplayer/editor/replication_editor_plugin.h
+++ b/modules/multiplayer/editor/replication_editor.h
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* replication_editor_plugin.h */
+/* replication_editor.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,21 +28,21 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#ifndef REPLICATION_EDITOR_PLUGIN_H
-#define REPLICATION_EDITOR_PLUGIN_H
+#ifndef REPLICATION_EDITOR_H
+#define REPLICATION_EDITOR_H
#include "editor/editor_plugin.h"
-
-#include "editor/editor_spin_slider.h"
-#include "editor/property_selector.h"
-
-#include "../scene_replication_config.h"
+#include "modules/multiplayer/scene_replication_config.h"
+#include "scene/gui/box_container.h"
class ConfirmationDialog;
class MultiplayerSynchronizer;
-class SceneTreeDialog;
+class AcceptDialog;
+class LineEdit;
class Tree;
class TreeItem;
+class PropertySelector;
+class SceneTreeDialog;
class ReplicationEditor : public VBoxContainer {
GDCLASS(ReplicationEditor, VBoxContainer);
@@ -105,27 +105,4 @@ public:
~ReplicationEditor() {}
};
-class ReplicationEditorPlugin : public EditorPlugin {
- GDCLASS(ReplicationEditorPlugin, EditorPlugin);
-
-private:
- Button *button = nullptr;
- ReplicationEditor *repl_editor = nullptr;
-
- void _node_removed(Node *p_node);
-
- void _pinned();
-
-protected:
- void _notification(int p_what);
-
-public:
- virtual void edit(Object *p_object) override;
- virtual bool handles(Object *p_object) const override;
- virtual void make_visible(bool p_visible) override;
-
- ReplicationEditorPlugin();
- ~ReplicationEditorPlugin();
-};
-
-#endif // REPLICATION_EDITOR_PLUGIN_H
+#endif // REPLICATION_EDITOR_H
diff --git a/modules/multiplayer/multiplayer_debugger.cpp b/modules/multiplayer/multiplayer_debugger.cpp
new file mode 100644
index 0000000000..3d22af04dc
--- /dev/null
+++ b/modules/multiplayer/multiplayer_debugger.cpp
@@ -0,0 +1,194 @@
+/*************************************************************************/
+/* multiplayer_debugger.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "multiplayer_debugger.h"
+
+#include "core/debugger/engine_debugger.h"
+#include "scene/main/node.h"
+
+List<Ref<EngineProfiler>> multiplayer_profilers;
+
+void MultiplayerDebugger::initialize() {
+ Ref<BandwidthProfiler> bandwidth;
+ bandwidth.instantiate();
+ bandwidth->bind("multiplayer");
+ multiplayer_profilers.push_back(bandwidth);
+
+ Ref<RPCProfiler> rpc_profiler;
+ rpc_profiler.instantiate();
+ rpc_profiler->bind("rpc");
+ multiplayer_profilers.push_back(rpc_profiler);
+}
+
+void MultiplayerDebugger::deinitialize() {
+ multiplayer_profilers.clear();
+}
+
+// BandwidthProfiler
+
+int MultiplayerDebugger::BandwidthProfiler::bandwidth_usage(const Vector<BandwidthFrame> &p_buffer, int p_pointer) {
+ ERR_FAIL_COND_V(p_buffer.size() == 0, 0);
+ int total_bandwidth = 0;
+
+ uint64_t timestamp = OS::get_singleton()->get_ticks_msec();
+ uint64_t final_timestamp = timestamp - 1000;
+
+ int i = (p_pointer + p_buffer.size() - 1) % p_buffer.size();
+
+ while (i != p_pointer && p_buffer[i].packet_size > 0) {
+ if (p_buffer[i].timestamp < final_timestamp) {
+ return total_bandwidth;
+ }
+ total_bandwidth += p_buffer[i].packet_size;
+ i = (i + p_buffer.size() - 1) % p_buffer.size();
+ }
+
+ ERR_FAIL_COND_V_MSG(i == p_pointer, total_bandwidth, "Reached the end of the bandwidth profiler buffer, values might be inaccurate.");
+ return total_bandwidth;
+}
+
+void MultiplayerDebugger::BandwidthProfiler::toggle(bool p_enable, const Array &p_opts) {
+ if (!p_enable) {
+ bandwidth_in.clear();
+ bandwidth_out.clear();
+ } else {
+ bandwidth_in_ptr = 0;
+ bandwidth_in.resize(16384); // ~128kB
+ for (int i = 0; i < bandwidth_in.size(); ++i) {
+ bandwidth_in.write[i].packet_size = -1;
+ }
+ bandwidth_out_ptr = 0;
+ bandwidth_out.resize(16384); // ~128kB
+ for (int i = 0; i < bandwidth_out.size(); ++i) {
+ bandwidth_out.write[i].packet_size = -1;
+ }
+ }
+}
+
+void MultiplayerDebugger::BandwidthProfiler::add(const Array &p_data) {
+ ERR_FAIL_COND(p_data.size() < 3);
+ const String inout = p_data[0];
+ int time = p_data[1];
+ int size = p_data[2];
+ if (inout == "in") {
+ bandwidth_in.write[bandwidth_in_ptr].timestamp = time;
+ bandwidth_in.write[bandwidth_in_ptr].packet_size = size;
+ bandwidth_in_ptr = (bandwidth_in_ptr + 1) % bandwidth_in.size();
+ } else if (inout == "out") {
+ bandwidth_out.write[bandwidth_out_ptr].timestamp = time;
+ bandwidth_out.write[bandwidth_out_ptr].packet_size = size;
+ bandwidth_out_ptr = (bandwidth_out_ptr + 1) % bandwidth_out.size();
+ }
+}
+
+void MultiplayerDebugger::BandwidthProfiler::tick(double p_frame_time, double p_process_time, double p_physics_time, double p_physics_frame_time) {
+ uint64_t pt = OS::get_singleton()->get_ticks_msec();
+ if (pt - last_bandwidth_time > 200) {
+ last_bandwidth_time = pt;
+ int incoming_bandwidth = bandwidth_usage(bandwidth_in, bandwidth_in_ptr);
+ int outgoing_bandwidth = bandwidth_usage(bandwidth_out, bandwidth_out_ptr);
+
+ Array arr;
+ arr.push_back(incoming_bandwidth);
+ arr.push_back(outgoing_bandwidth);
+ EngineDebugger::get_singleton()->send_message("multiplayer:bandwidth", arr);
+ }
+}
+
+// RPCProfiler
+
+Array MultiplayerDebugger::RPCFrame::serialize() {
+ Array arr;
+ arr.push_back(infos.size() * 4);
+ for (int i = 0; i < infos.size(); ++i) {
+ arr.push_back(uint64_t(infos[i].node));
+ arr.push_back(infos[i].node_path);
+ arr.push_back(infos[i].incoming_rpc);
+ arr.push_back(infos[i].outgoing_rpc);
+ }
+ return arr;
+}
+
+bool MultiplayerDebugger::RPCFrame::deserialize(const Array &p_arr) {
+ ERR_FAIL_COND_V(p_arr.size() < 1, false);
+ uint32_t size = p_arr[0];
+ ERR_FAIL_COND_V(size % 4, false);
+ ERR_FAIL_COND_V((uint32_t)p_arr.size() != size + 1, false);
+ infos.resize(size / 4);
+ int idx = 1;
+ for (uint32_t i = 0; i < size / 4; ++i) {
+ infos.write[i].node = uint64_t(p_arr[idx]);
+ infos.write[i].node_path = p_arr[idx + 1];
+ infos.write[i].incoming_rpc = p_arr[idx + 2];
+ infos.write[i].outgoing_rpc = p_arr[idx + 3];
+ }
+ return true;
+}
+
+void MultiplayerDebugger::RPCProfiler::init_node(const ObjectID p_node) {
+ if (rpc_node_data.has(p_node)) {
+ return;
+ }
+ rpc_node_data.insert(p_node, RPCNodeInfo());
+ rpc_node_data[p_node].node = p_node;
+ rpc_node_data[p_node].node_path = Object::cast_to<Node>(ObjectDB::get_instance(p_node))->get_path();
+ rpc_node_data[p_node].incoming_rpc = 0;
+ rpc_node_data[p_node].outgoing_rpc = 0;
+}
+
+void MultiplayerDebugger::RPCProfiler::toggle(bool p_enable, const Array &p_opts) {
+ rpc_node_data.clear();
+}
+
+void MultiplayerDebugger::RPCProfiler::add(const Array &p_data) {
+ ERR_FAIL_COND(p_data.size() < 2);
+ const ObjectID id = p_data[0];
+ const String what = p_data[1];
+ init_node(id);
+ RPCNodeInfo &info = rpc_node_data[id];
+ if (what == "rpc_in") {
+ info.incoming_rpc++;
+ } else if (what == "rpc_out") {
+ info.outgoing_rpc++;
+ }
+}
+
+void MultiplayerDebugger::RPCProfiler::tick(double p_frame_time, double p_process_time, double p_physics_time, double p_physics_frame_time) {
+ uint64_t pt = OS::get_singleton()->get_ticks_msec();
+ if (pt - last_profile_time > 100) {
+ last_profile_time = pt;
+ RPCFrame frame;
+ for (const KeyValue<ObjectID, RPCNodeInfo> &E : rpc_node_data) {
+ frame.infos.push_back(E.value);
+ }
+ rpc_node_data.clear();
+ EngineDebugger::get_singleton()->send_message("multiplayer:rpc", frame.serialize());
+ }
+}
diff --git a/modules/multiplayer/multiplayer_debugger.h b/modules/multiplayer/multiplayer_debugger.h
new file mode 100644
index 0000000000..4efd1da016
--- /dev/null
+++ b/modules/multiplayer/multiplayer_debugger.h
@@ -0,0 +1,95 @@
+/*************************************************************************/
+/* multiplayer_debugger.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef MULTIPLAYER_DEBUGGER_H
+#define MULTIPLAYER_DEBUGGER_H
+
+#include "core/debugger/engine_profiler.h"
+
+#include "core/os/os.h"
+
+class MultiplayerDebugger {
+public:
+ struct RPCNodeInfo {
+ ObjectID node;
+ String node_path;
+ int incoming_rpc = 0;
+ int outgoing_rpc = 0;
+ };
+
+ struct RPCFrame {
+ Vector<RPCNodeInfo> infos;
+
+ Array serialize();
+ bool deserialize(const Array &p_arr);
+ };
+
+private:
+ class BandwidthProfiler : public EngineProfiler {
+ protected:
+ struct BandwidthFrame {
+ uint32_t timestamp;
+ int packet_size;
+ };
+
+ int bandwidth_in_ptr = 0;
+ Vector<BandwidthFrame> bandwidth_in;
+ int bandwidth_out_ptr = 0;
+ Vector<BandwidthFrame> bandwidth_out;
+ uint64_t last_bandwidth_time = 0;
+
+ int bandwidth_usage(const Vector<BandwidthFrame> &p_buffer, int p_pointer);
+
+ public:
+ void toggle(bool p_enable, const Array &p_opts);
+ void add(const Array &p_data);
+ void tick(double p_frame_time, double p_process_time, double p_physics_time, double p_physics_frame_time);
+ };
+
+ class RPCProfiler : public EngineProfiler {
+ public:
+ private:
+ HashMap<ObjectID, RPCNodeInfo> rpc_node_data;
+ uint64_t last_profile_time = 0;
+
+ void init_node(const ObjectID p_node);
+
+ public:
+ void toggle(bool p_enable, const Array &p_opts);
+ void add(const Array &p_data);
+ void tick(double p_frame_time, double p_process_time, double p_physics_time, double p_physics_frame_time);
+ };
+
+public:
+ static void initialize();
+ static void deinitialize();
+};
+
+#endif // MULTIPLAYER_DEBUGGER_H
diff --git a/modules/multiplayer/register_types.cpp b/modules/multiplayer/register_types.cpp
index a2c524da80..2bf1041029 100644
--- a/modules/multiplayer/register_types.cpp
+++ b/modules/multiplayer/register_types.cpp
@@ -36,9 +36,10 @@
#include "scene_replication_interface.h"
#include "scene_rpc_interface.h"
+#include "multiplayer_debugger.h"
+
#ifdef TOOLS_ENABLED
-#include "editor/editor_plugin.h"
-#include "editor/replication_editor_plugin.h"
+#include "editor/multiplayer_editor_plugin.h"
#endif
void initialize_multiplayer_module(ModuleInitializationLevel p_level) {
@@ -48,13 +49,15 @@ void initialize_multiplayer_module(ModuleInitializationLevel p_level) {
GDREGISTER_CLASS(MultiplayerSynchronizer);
GDREGISTER_CLASS(SceneMultiplayer);
MultiplayerAPI::set_default_interface("SceneMultiplayer");
+ MultiplayerDebugger::initialize();
}
#ifdef TOOLS_ENABLED
if (p_level == MODULE_INITIALIZATION_LEVEL_EDITOR) {
- EditorPlugins::add_by_type<ReplicationEditorPlugin>();
+ EditorPlugins::add_by_type<MultiplayerEditorPlugin>();
}
#endif
}
void uninitialize_multiplayer_module(ModuleInitializationLevel p_level) {
+ MultiplayerDebugger::deinitialize();
}
diff --git a/modules/navigation/editor/navigation_mesh_editor_plugin.h b/modules/navigation/editor/navigation_mesh_editor_plugin.h
index bc9e4185b7..b7bde98131 100644
--- a/modules/navigation/editor/navigation_mesh_editor_plugin.h
+++ b/modules/navigation/editor/navigation_mesh_editor_plugin.h
@@ -35,6 +35,8 @@
#include "editor/editor_plugin.h"
+class AcceptDialog;
+class HBoxContainer;
class NavigationRegion3D;
class NavigationMeshEditor : public Control {
diff --git a/modules/noise/editor/noise_editor_plugin.cpp b/modules/noise/editor/noise_editor_plugin.cpp
index e8e73e4fd9..47f5f8f819 100644
--- a/modules/noise/editor/noise_editor_plugin.cpp
+++ b/modules/noise/editor/noise_editor_plugin.cpp
@@ -32,7 +32,9 @@
#ifdef TOOLS_ENABLED
+#include "editor/editor_inspector.h"
#include "editor/editor_scale.h"
+#include "scene/gui/texture_rect.h"
#include "modules/noise/noise.h"
#include "modules/noise/noise_texture_2d.h"
diff --git a/modules/openxr/extensions/openxr_opengl_extension.cpp b/modules/openxr/extensions/openxr_opengl_extension.cpp
index a25727f885..ee69144123 100644
--- a/modules/openxr/extensions/openxr_opengl_extension.cpp
+++ b/modules/openxr/extensions/openxr_opengl_extension.cpp
@@ -151,19 +151,6 @@ void *OpenXROpenGLExtension::set_session_create_and_get_next_pointer(void *p_nex
graphics_binding_gl.glxContext = (GLXContext)glxcontext_handle;
graphics_binding_gl.glxDrawable = (GLXDrawable)glxdrawable_handle;
- if (graphics_binding_gl.xDisplay == nullptr) {
- print_line("OpenXR Failed to get xDisplay from Godot, using XOpenDisplay(nullptr)");
- graphics_binding_gl.xDisplay = XOpenDisplay(nullptr);
- }
- if (graphics_binding_gl.glxContext == nullptr) {
- print_line("OpenXR Failed to get glxContext from Godot, using glXGetCurrentContext()");
- graphics_binding_gl.glxContext = glXGetCurrentContext();
- }
- if (graphics_binding_gl.glxDrawable == 0) {
- print_line("OpenXR Failed to get glxDrawable from Godot, using glXGetCurrentDrawable()");
- graphics_binding_gl.glxDrawable = glXGetCurrentDrawable();
- }
-
// spec says to use proper values but runtimes don't care
graphics_binding_gl.visualid = 0;
graphics_binding_gl.glxFBConfig = 0;
diff --git a/modules/openxr/openxr_interface.cpp b/modules/openxr/openxr_interface.cpp
index be6b7e4411..77660eb6f0 100644
--- a/modules/openxr/openxr_interface.cpp
+++ b/modules/openxr/openxr_interface.cpp
@@ -666,6 +666,7 @@ Transform3D OpenXRInterface::get_camera_transform() {
Transform3D OpenXRInterface::get_transform_for_view(uint32_t p_view, const Transform3D &p_cam_transform) {
XRServer *xr_server = XRServer::get_singleton();
ERR_FAIL_NULL_V(xr_server, Transform3D());
+ ERR_FAIL_UNSIGNED_INDEX_V_MSG(p_view, get_view_count(), Transform3D(), "View index outside bounds.");
Transform3D t;
if (openxr_api && openxr_api->get_view_transform(p_view, t)) {
@@ -685,6 +686,7 @@ Transform3D OpenXRInterface::get_transform_for_view(uint32_t p_view, const Trans
Projection OpenXRInterface::get_projection_for_view(uint32_t p_view, double p_aspect, double p_z_near, double p_z_far) {
Projection cm;
+ ERR_FAIL_UNSIGNED_INDEX_V_MSG(p_view, get_view_count(), cm, "View index outside bounds.");
if (openxr_api) {
if (openxr_api->get_view_projection(p_view, p_z_near, p_z_far, cm)) {
diff --git a/modules/text_server_adv/SCsub b/modules/text_server_adv/SCsub
index bd208a8429..3c7a89b705 100644
--- a/modules/text_server_adv/SCsub
+++ b/modules/text_server_adv/SCsub
@@ -39,6 +39,9 @@ thirdparty_obj = []
freetype_enabled = "freetype" in env.module_list
msdfgen_enabled = "msdfgen" in env.module_list
+if "svg" in env.module_list:
+ env_text_server_adv.Prepend(CPPPATH=["#thirdparty/thorvg/inc", "#thirdparty/thorvg/src/lib"])
+
if env["builtin_harfbuzz"]:
env_harfbuzz = env_modules.Clone()
env_harfbuzz.disable_warnings()
diff --git a/modules/text_server_adv/gdextension_build/SConstruct b/modules/text_server_adv/gdextension_build/SConstruct
index 65b41e46ce..4a363fdd7a 100644
--- a/modules/text_server_adv/gdextension_build/SConstruct
+++ b/modules/text_server_adv/gdextension_build/SConstruct
@@ -23,6 +23,7 @@ opts.Add(BoolVariable("brotli_enabled", "Use Brotli library", True))
opts.Add(BoolVariable("freetype_enabled", "Use FreeType library", True))
opts.Add(BoolVariable("msdfgen_enabled", "Use MSDFgen library (require FreeType)", True))
opts.Add(BoolVariable("graphite_enabled", "Use Graphite library (require FreeType)", True))
+opts.Add(BoolVariable("thorvg_enabled", "Use ThorVG library (require FreeType)", True))
opts.Add(BoolVariable("static_icu_data", "Use built-in ICU data", True))
opts.Add(BoolVariable("verbose", "Enable verbose output for the compilation", False))
@@ -34,6 +35,79 @@ if not env["verbose"]:
if env["platform"] == "windows" and not env["use_mingw"]:
env.AppendUnique(CCFLAGS=["/utf-8"]) # Force to use Unicode encoding.
+# ThorVG
+if env["thorvg_enabled"] and env["freetype_enabled"]:
+ env_tvg = env.Clone()
+ env_tvg.disable_warnings()
+
+ thirdparty_tvg_dir = "../../../thirdparty/thorvg/"
+ thirdparty_tvg_sources = [
+ "src/lib/sw_engine/tvgSwFill.cpp",
+ "src/lib/sw_engine/tvgSwImage.cpp",
+ "src/lib/sw_engine/tvgSwMath.cpp",
+ "src/lib/sw_engine/tvgSwMemPool.cpp",
+ "src/lib/sw_engine/tvgSwRaster.cpp",
+ "src/lib/sw_engine/tvgSwRenderer.cpp",
+ "src/lib/sw_engine/tvgSwRle.cpp",
+ "src/lib/sw_engine/tvgSwShape.cpp",
+ "src/lib/sw_engine/tvgSwStroke.cpp",
+ "src/lib/tvgAccessor.cpp",
+ "src/lib/tvgBezier.cpp",
+ "src/lib/tvgCanvas.cpp",
+ "src/lib/tvgFill.cpp",
+ "src/lib/tvgGlCanvas.cpp",
+ "src/lib/tvgInitializer.cpp",
+ "src/lib/tvgLinearGradient.cpp",
+ "src/lib/tvgLoader.cpp",
+ "src/lib/tvgLzw.cpp",
+ "src/lib/tvgPaint.cpp",
+ "src/lib/tvgPicture.cpp",
+ "src/lib/tvgRadialGradient.cpp",
+ "src/lib/tvgRender.cpp",
+ "src/lib/tvgSaver.cpp",
+ "src/lib/tvgScene.cpp",
+ "src/lib/tvgShape.cpp",
+ "src/lib/tvgSwCanvas.cpp",
+ "src/lib/tvgTaskScheduler.cpp",
+ "src/loaders/external_png/tvgPngLoader.cpp",
+ "src/loaders/jpg/tvgJpgd.cpp",
+ "src/loaders/jpg/tvgJpgLoader.cpp",
+ "src/loaders/raw/tvgRawLoader.cpp",
+ "src/loaders/svg/tvgSvgCssStyle.cpp",
+ "src/loaders/svg/tvgSvgLoader.cpp",
+ "src/loaders/svg/tvgSvgPath.cpp",
+ "src/loaders/svg/tvgSvgSceneBuilder.cpp",
+ "src/loaders/svg/tvgSvgUtil.cpp",
+ "src/loaders/svg/tvgXmlParser.cpp",
+ "src/loaders/tvg/tvgTvgBinInterpreter.cpp",
+ "src/loaders/tvg/tvgTvgLoader.cpp",
+ "src/savers/tvg/tvgTvgSaver.cpp",
+ ]
+ thirdparty_tvg_sources = [thirdparty_tvg_dir + file for file in thirdparty_tvg_sources]
+
+ env_tvg.Append(
+ CPPPATH=[
+ "../../../thirdparty/thorvg/inc",
+ "../../../thirdparty/thorvg/src/lib",
+ "../../../thirdparty/thorvg/src/lib/sw_engine",
+ "../../../thirdparty/thorvg/src/loaders/external_png",
+ "../../../thirdparty/thorvg/src/loaders/jpg",
+ "../../../thirdparty/thorvg/src/loaders/raw",
+ "../../../thirdparty/thorvg/src/loaders/svg",
+ "../../../thirdparty/thorvg/src/loaders/tvg",
+ "../../../thirdparty/thorvg/src/savers/tvg",
+ "../../../thirdparty/libpng",
+ ]
+ )
+ env.Append(CPPPATH=["../../../thirdparty/thorvg/inc", "../../../thirdparty/thorvg/src/lib"])
+ env.Append(CPPDEFINES=["MODULE_SVG_ENABLED"])
+
+ lib = env_tvg.Library(
+ f'tvg_builtin{env["suffix"]}{env["LIBSUFFIX"]}',
+ thirdparty_tvg_sources,
+ )
+ env.Append(LIBS=[lib])
+
# MSDFGEN
if env["msdfgen_enabled"] and env["freetype_enabled"]:
env_msdfgen = env.Clone()
diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp
index 155da3c67b..166325c551 100644
--- a/modules/text_server_adv/text_server_adv.cpp
+++ b/modules/text_server_adv/text_server_adv.cpp
@@ -41,6 +41,8 @@
using namespace godot;
+#define GLOBAL_GET(m_var) ProjectSettings::get_singleton()->get(m_var)
+
#else
// Headers for building as built-in module.
@@ -50,7 +52,7 @@ using namespace godot;
#include "core/string/print_string.h"
#include "core/string/translation.h"
-#include "modules/modules_enabled.gen.h" // For freetype, msdfgen.
+#include "modules/modules_enabled.gen.h" // For freetype, msdfgen, svg.
#endif
@@ -69,6 +71,10 @@ using namespace godot;
#include "msdfgen.h"
#endif
+#ifdef MODULE_SVG_ENABLED
+#include "thorvg_svg_in_ot.h"
+#endif
+
/*************************************************************************/
/* bmp_font_t HarfBuzz Bitmap font interface */
/*************************************************************************/
@@ -1346,6 +1352,9 @@ _FORCE_INLINE_ bool TextServerAdvanced::_ensure_cache_for_size(FontAdvanced *p_f
memdelete(fd);
ERR_FAIL_V_MSG(false, "FreeType: Error initializing library: '" + String(FT_Error_String(error)) + "'.");
}
+#ifdef MODULE_SVG_ENABLED
+ FT_Property_Set(ft_library, "ot-svg", "svg-hooks", get_tvg_svg_in_ot_hooks());
+#endif
}
memset(&fd->stream, 0, sizeof(FT_StreamRec));
@@ -1888,6 +1897,9 @@ int64_t TextServerAdvanced::_font_get_face_count(const RID &p_font_rid) const {
if (!ft_library) {
error = FT_Init_FreeType(&ft_library);
ERR_FAIL_COND_V_MSG(error != 0, false, "FreeType: Error initializing library: '" + String(FT_Error_String(error)) + "'.");
+#ifdef MODULE_SVG_ENABLED
+ FT_Property_Set(ft_library, "ot-svg", "svg-hooks", get_tvg_svg_in_ot_hooks());
+#endif
}
FT_StreamRec stream;
diff --git a/modules/text_server_adv/text_server_adv.h b/modules/text_server_adv/text_server_adv.h
index 33fa1e117e..10fe3c2316 100644
--- a/modules/text_server_adv/text_server_adv.h
+++ b/modules/text_server_adv/text_server_adv.h
@@ -88,7 +88,7 @@ using namespace godot;
#include "core/templates/rid_owner.h"
#include "scene/resources/texture.h"
-#include "modules/modules_enabled.gen.h" // For freetype, msdfgen.
+#include "modules/modules_enabled.gen.h" // For freetype, msdfgen, svg.
#endif
@@ -117,6 +117,7 @@ using namespace godot;
#include FT_ADVANCES_H
#include FT_MULTIPLE_MASTERS_H
#include FT_BBOX_H
+#include FT_MODULE_H
#include FT_CONFIG_OPTIONS_H
#if !defined(FT_CONFIG_OPTION_USE_BROTLI) && !defined(_MSC_VER)
#warning FreeType is configured without Brotli support, built-in fonts will not be available.
diff --git a/modules/text_server_adv/thorvg_bounds_iterator.cpp b/modules/text_server_adv/thorvg_bounds_iterator.cpp
new file mode 100644
index 0000000000..54a6136134
--- /dev/null
+++ b/modules/text_server_adv/thorvg_bounds_iterator.cpp
@@ -0,0 +1,70 @@
+/*************************************************************************/
+/* thorvg_bounds_iterator.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifdef GDEXTENSION
+// Headers for building as GDExtension plug-in.
+
+#include <godot_cpp/godot.hpp>
+
+using namespace godot;
+
+#else
+// Headers for building as built-in module.
+
+#include "core/typedefs.h"
+
+#include "modules/modules_enabled.gen.h" // For svg.
+#endif
+
+#ifdef MODULE_SVG_ENABLED
+
+#include "thorvg_bounds_iterator.h"
+
+#include <tvgIteratorAccessor.h>
+#include <tvgPaint.h>
+
+// This function uses private ThorVG API to get bounding box of top level children elements.
+
+void tvg_get_bounds(tvg::Picture *p_picture, float &r_min_x, float &r_min_y, float &r_max_x, float &r_max_y) {
+ tvg::IteratorAccessor itrAccessor;
+ if (tvg::Iterator *it = itrAccessor.iterator(p_picture)) {
+ while (const tvg::Paint *child = it->next()) {
+ float x = 0, y = 0, w = 0, h = 0;
+ child->bounds(&x, &y, &w, &h, true);
+ r_min_x = MIN(x, r_min_x);
+ r_min_y = MIN(y, r_min_y);
+ r_max_x = MAX(x + w, r_max_x);
+ r_max_y = MAX(y + h, r_max_y);
+ }
+ delete (it);
+ }
+}
+
+#endif // MODULE_SVG_ENABLED
diff --git a/modules/text_server_adv/thorvg_bounds_iterator.h b/modules/text_server_adv/thorvg_bounds_iterator.h
new file mode 100644
index 0000000000..e54e30eaa2
--- /dev/null
+++ b/modules/text_server_adv/thorvg_bounds_iterator.h
@@ -0,0 +1,58 @@
+/*************************************************************************/
+/* thorvg_bounds_iterator.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef THORVG_BOUNDS_ITERATOR_H
+#define THORVG_BOUNDS_ITERATOR_H
+
+#ifdef GDEXTENSION
+// Headers for building as GDExtension plug-in.
+
+#include <godot_cpp/core/mutex_lock.hpp>
+#include <godot_cpp/godot.hpp>
+
+using namespace godot;
+
+#else
+// Headers for building as built-in module.
+
+#include "core/typedefs.h"
+
+#include "modules/modules_enabled.gen.h" // For svg.
+#endif
+
+#ifdef MODULE_SVG_ENABLED
+
+#include <thorvg.h>
+
+void tvg_get_bounds(tvg::Picture *p_picture, float &r_min_x, float &r_min_y, float &r_max_x, float &r_max_y);
+
+#endif // MODULE_SVG_ENABLED
+
+#endif // THORVG_BOUNDS_ITERATOR_H
diff --git a/modules/text_server_adv/thorvg_svg_in_ot.cpp b/modules/text_server_adv/thorvg_svg_in_ot.cpp
new file mode 100644
index 0000000000..7863ab67fa
--- /dev/null
+++ b/modules/text_server_adv/thorvg_svg_in_ot.cpp
@@ -0,0 +1,320 @@
+/*************************************************************************/
+/* thorvg_svg_in_ot.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifdef GDEXTENSION
+// Headers for building as GDExtension plug-in.
+
+#include <godot_cpp/classes/xml_parser.hpp>
+#include <godot_cpp/core/mutex_lock.hpp>
+#include <godot_cpp/godot.hpp>
+#include <godot_cpp/templates/vector.hpp>
+
+using namespace godot;
+
+#else
+// Headers for building as built-in module.
+
+#include "core/error/error_macros.h"
+#include "core/io/xml_parser.h"
+#include "core/os/memory.h"
+#include "core/os/os.h"
+#include "core/string/ustring.h"
+#include "core/typedefs.h"
+#include "core/variant/variant.h"
+
+#include "modules/modules_enabled.gen.h" // For svg.
+#endif
+
+#ifdef MODULE_SVG_ENABLED
+
+#include "thorvg_bounds_iterator.h"
+#include "thorvg_svg_in_ot.h"
+
+#include <freetype/otsvg.h>
+#include <ft2build.h>
+
+#include <math.h>
+#include <stdlib.h>
+
+FT_Error tvg_svg_in_ot_init(FT_Pointer *p_state) {
+ *p_state = memnew(TVG_State);
+
+ return FT_Err_Ok;
+}
+
+void tvg_svg_in_ot_free(FT_Pointer *p_state) {
+ TVG_State *state = *reinterpret_cast<TVG_State **>(p_state);
+ memdelete(state);
+}
+
+FT_Error tvg_svg_in_ot_preset_slot(FT_GlyphSlot p_slot, FT_Bool p_cache, FT_Pointer *p_state) {
+ TVG_State *state = *reinterpret_cast<TVG_State **>(p_state);
+ if (!state) {
+ ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "SVG in OT state not initialized.");
+ }
+ MutexLock lock(state->mutex);
+
+ FT_SVG_Document document = (FT_SVG_Document)p_slot->other;
+ FT_Size_Metrics metrics = document->metrics;
+
+ GL_State &gl_state = state->glyph_map[p_slot->glyph_index];
+ if (!gl_state.ready) {
+ Ref<XMLParser> parser;
+ parser.instantiate();
+#ifdef GDEXTENSION
+ PackedByteArray data;
+ data.resize(document->svg_document_length);
+ memcpy(data.ptrw(), document->svg_document, document->svg_document_length);
+ parser->open_buffer(data);
+#else
+ parser->_open_buffer((const uint8_t *)document->svg_document, document->svg_document_length);
+#endif
+
+ float aspect = 1.0f;
+ String xml_body;
+ while (parser->read() == OK) {
+ if (parser->has_attribute("id")) {
+#ifdef GDEXTENSION
+ const String &gl_name = parser->get_named_attribute_value("id");
+#else
+ const String &gl_name = parser->get_attribute_value("id");
+#endif
+ if (gl_name.begins_with("glyph")) {
+ int dot_pos = gl_name.find(".");
+ int64_t gl_idx = gl_name.substr(5, (dot_pos > 0) ? dot_pos - 5 : -1).to_int();
+ if (p_slot->glyph_index != gl_idx) {
+ parser->skip_section();
+ continue;
+ }
+ }
+ }
+ if (parser->get_node_type() == XMLParser::NODE_ELEMENT && parser->get_node_name() == "svg") {
+ if (parser->has_attribute("viewBox")) {
+#ifdef GDEXTENSION
+ PackedStringArray vb = parser->get_named_attribute_value("viewBox").split(" ");
+#else
+ Vector<String> vb = parser->get_attribute_value("viewBox").split(" ");
+#endif
+
+ if (vb.size() == 4) {
+ aspect = vb[2].to_float() / vb[3].to_float();
+ }
+ }
+ continue;
+ }
+#ifdef GDEXTENSION
+ if (parser->get_node_type() == XMLParser::NODE_ELEMENT) {
+ xml_body = xml_body + "<" + parser->get_node_name();
+ for (int i = 0; i < parser->get_attribute_count(); i++) {
+ xml_body = xml_body + " " + parser->get_attribute_name(i) + "=\"" + parser->get_attribute_value(i) + "\"";
+ }
+ xml_body = xml_body + ">";
+ } else if (parser->get_node_type() == XMLParser::NODE_TEXT) {
+ xml_body = xml_body + parser->get_node_data();
+ } else if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END) {
+ xml_body = xml_body + "</" + parser->get_node_name() + ">";
+ }
+#else
+ if (parser->get_node_type() == XMLParser::NODE_ELEMENT) {
+ xml_body += vformat("<%s", parser->get_node_name());
+ for (int i = 0; i < parser->get_attribute_count(); i++) {
+ xml_body += vformat(" %s=\"%s\"", parser->get_attribute_name(i), parser->get_attribute_value(i));
+ }
+ xml_body += ">";
+ } else if (parser->get_node_type() == XMLParser::NODE_TEXT) {
+ xml_body += parser->get_node_data();
+ } else if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END) {
+ xml_body += vformat("</%s>", parser->get_node_name());
+ }
+#endif
+ }
+ String temp_xml = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 0 0\">" + xml_body;
+
+ std::unique_ptr<tvg::Picture> picture = tvg::Picture::gen();
+ tvg::Result result = picture->load(temp_xml.utf8().get_data(), temp_xml.utf8().length(), "svg+xml", false);
+ if (result != tvg::Result::Success) {
+ ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to load SVG document (bounds detection).");
+ }
+
+ float min_x = INFINITY, min_y = INFINITY, max_x = -INFINITY, max_y = -INFINITY;
+ tvg_get_bounds(picture.get(), min_x, min_y, max_x, max_y);
+
+ float new_h = (max_y - min_y);
+ float new_w = (max_x - min_x);
+
+ if (new_h * aspect >= new_w) {
+ new_w = (new_h * aspect);
+ } else {
+ new_h = (new_w / aspect);
+ }
+
+#ifdef GDEXTENSION
+ gl_state.xml_code = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"" + rtos(min_x) + " " + rtos(min_y) + " " + rtos(new_w) + " " + rtos(new_h) + "\">" + xml_body;
+#else
+ gl_state.xml_code = vformat("<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"%f %f %f %f\">", min_x, min_y, new_w, new_h) + xml_body;
+#endif
+
+ picture = tvg::Picture::gen();
+ result = picture->load(gl_state.xml_code.utf8().get_data(), gl_state.xml_code.utf8().length(), "svg+xml", false);
+ if (result != tvg::Result::Success) {
+ ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to load SVG document (glyph metrics).");
+ }
+
+ float x_svg_to_out, y_svg_to_out;
+ x_svg_to_out = (float)metrics.x_ppem / new_w;
+ y_svg_to_out = (float)metrics.y_ppem / new_h;
+
+ gl_state.m.e11 = (double)document->transform.xx / (1 << 16) * x_svg_to_out;
+ gl_state.m.e12 = -(double)document->transform.xy / (1 << 16) * x_svg_to_out;
+ gl_state.m.e21 = -(double)document->transform.yx / (1 << 16) * y_svg_to_out;
+ gl_state.m.e22 = (double)document->transform.yy / (1 << 16) * y_svg_to_out;
+ gl_state.m.e13 = (double)document->delta.x / 64 * new_w / metrics.x_ppem;
+ gl_state.m.e23 = -(double)document->delta.y / 64 * new_h / metrics.y_ppem;
+ gl_state.m.e31 = 0;
+ gl_state.m.e32 = 0;
+ gl_state.m.e33 = 1;
+
+ result = picture->transform(gl_state.m);
+ if (result != tvg::Result::Success) {
+ ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to apply transform to SVG document.");
+ }
+
+ result = picture->bounds(&gl_state.x, &gl_state.y, &gl_state.w, &gl_state.h, true);
+ if (result != tvg::Result::Success) {
+ ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to get SVG bounds.");
+ }
+
+ gl_state.bmp_y = -min_y * gl_state.h / new_h;
+ gl_state.bmp_x = min_x * gl_state.w / new_w;
+
+ gl_state.ready = true;
+ }
+
+ p_slot->bitmap_left = (FT_Int)gl_state.bmp_x;
+ p_slot->bitmap_top = (FT_Int)gl_state.bmp_y;
+
+ float tmp = ceil(gl_state.h);
+ p_slot->bitmap.rows = (unsigned int)tmp;
+ tmp = ceil(gl_state.w);
+ p_slot->bitmap.width = (unsigned int)tmp;
+ p_slot->bitmap.pitch = (int)p_slot->bitmap.width * 4;
+ p_slot->bitmap.pixel_mode = FT_PIXEL_MODE_BGRA;
+
+ float metrics_width, metrics_height;
+ float horiBearingX, horiBearingY;
+ float vertBearingX, vertBearingY;
+
+ metrics_width = (float)gl_state.w;
+ metrics_height = (float)gl_state.h;
+ horiBearingX = (float)gl_state.x;
+ horiBearingY = (float)-gl_state.y;
+ vertBearingX = p_slot->metrics.horiBearingX / 64.0f - p_slot->metrics.horiAdvance / 64.0f / 2;
+ vertBearingY = (p_slot->metrics.vertAdvance / 64.0f - p_slot->metrics.height / 64.0f) / 2;
+
+ tmp = roundf(metrics_width * 64);
+ p_slot->metrics.width = (FT_Pos)tmp;
+ tmp = roundf(metrics_height * 64);
+ p_slot->metrics.height = (FT_Pos)tmp;
+
+ p_slot->metrics.horiBearingX = (FT_Pos)(horiBearingX * 64);
+ p_slot->metrics.horiBearingY = (FT_Pos)(horiBearingY * 64);
+ p_slot->metrics.vertBearingX = (FT_Pos)(vertBearingX * 64);
+ p_slot->metrics.vertBearingY = (FT_Pos)(vertBearingY * 64);
+
+ if (p_slot->metrics.vertAdvance == 0) {
+ p_slot->metrics.vertAdvance = (FT_Pos)(metrics_height * 1.2f * 64);
+ }
+
+ return FT_Err_Ok;
+}
+
+FT_Error tvg_svg_in_ot_render(FT_GlyphSlot p_slot, FT_Pointer *p_state) {
+ TVG_State *state = *reinterpret_cast<TVG_State **>(p_state);
+ if (!state) {
+ ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "SVG in OT state not initialized.");
+ }
+ MutexLock lock(state->mutex);
+
+ if (!state->glyph_map.has(p_slot->glyph_index)) {
+ ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "SVG glyph not loaded.");
+ }
+
+ GL_State &gl_state = state->glyph_map[p_slot->glyph_index];
+ ERR_FAIL_COND_V_MSG(!gl_state.ready, FT_Err_Invalid_SVG_Document, "SVG glyph not ready.");
+
+ std::unique_ptr<tvg::Picture> picture = tvg::Picture::gen();
+ tvg::Result res = picture->load(gl_state.xml_code.utf8().get_data(), gl_state.xml_code.utf8().length(), "svg+xml", false);
+ if (res != tvg::Result::Success) {
+ ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to load SVG document (glyph rendering).");
+ }
+ res = picture->transform(gl_state.m);
+ if (res != tvg::Result::Success) {
+ ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to apply transform to SVG document.");
+ }
+
+ std::unique_ptr<tvg::SwCanvas> sw_canvas = tvg::SwCanvas::gen();
+ res = sw_canvas->target((uint32_t *)p_slot->bitmap.buffer, (int)p_slot->bitmap.width, (int)p_slot->bitmap.width, (int)p_slot->bitmap.rows, tvg::SwCanvas::ARGB8888_STRAIGHT);
+ if (res != tvg::Result::Success) {
+ ERR_FAIL_V_MSG(FT_Err_Invalid_Outline, "Failed to create SVG canvas.");
+ }
+ res = sw_canvas->push(std::move(picture));
+ if (res != tvg::Result::Success) {
+ ERR_FAIL_V_MSG(FT_Err_Invalid_Outline, "Failed to set SVG canvas source.");
+ }
+ res = sw_canvas->draw();
+ if (res != tvg::Result::Success) {
+ ERR_FAIL_V_MSG(FT_Err_Invalid_Outline, "Failed to draw to SVG canvas.");
+ }
+ res = sw_canvas->sync();
+ if (res != tvg::Result::Success) {
+ ERR_FAIL_V_MSG(FT_Err_Invalid_Outline, "Failed to sync SVG canvas.");
+ }
+
+ state->glyph_map.erase(p_slot->glyph_index);
+
+ p_slot->bitmap.pixel_mode = FT_PIXEL_MODE_BGRA;
+ p_slot->bitmap.num_grays = 256;
+ p_slot->format = FT_GLYPH_FORMAT_BITMAP;
+
+ return FT_Err_Ok;
+}
+
+SVG_RendererHooks tvg_svg_in_ot_hooks = {
+ (SVG_Lib_Init_Func)tvg_svg_in_ot_init,
+ (SVG_Lib_Free_Func)tvg_svg_in_ot_free,
+ (SVG_Lib_Render_Func)tvg_svg_in_ot_render,
+ (SVG_Lib_Preset_Slot_Func)tvg_svg_in_ot_preset_slot,
+};
+
+SVG_RendererHooks *get_tvg_svg_in_ot_hooks() {
+ return &tvg_svg_in_ot_hooks;
+}
+
+#endif // MODULE_SVG_ENABLED
diff --git a/modules/text_server_adv/thorvg_svg_in_ot.h b/modules/text_server_adv/thorvg_svg_in_ot.h
new file mode 100644
index 0000000000..b2816193d9
--- /dev/null
+++ b/modules/text_server_adv/thorvg_svg_in_ot.h
@@ -0,0 +1,86 @@
+/*************************************************************************/
+/* thorvg_svg_in_ot.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef THORVG_SVG_IN_OT_H
+#define THORVG_SVG_IN_OT_H
+
+#ifdef GDEXTENSION
+// Headers for building as GDExtension plug-in.
+
+#include <godot_cpp/core/mutex_lock.hpp>
+#include <godot_cpp/godot.hpp>
+#include <godot_cpp/templates/hash_map.hpp>
+
+using namespace godot;
+
+#else
+// Headers for building as built-in module.
+
+#include "core/os/mutex.h"
+#include "core/templates/hash_map.h"
+#include "core/typedefs.h"
+
+#include "modules/modules_enabled.gen.h" // For svg.
+#endif
+
+#ifdef MODULE_SVG_ENABLED
+
+#include <freetype/freetype.h>
+#include <freetype/otsvg.h>
+#include <ft2build.h>
+#include <thorvg.h>
+
+struct GL_State {
+ bool ready = false;
+ float bmp_x = 0;
+ float bmp_y = 0;
+ float x = 0;
+ float y = 0;
+ float w = 0;
+ float h = 0;
+ String xml_code;
+ tvg::Matrix m;
+};
+
+struct TVG_State {
+ Mutex mutex;
+ HashMap<uint32_t, GL_State> glyph_map;
+};
+
+FT_Error tvg_svg_in_ot_init(FT_Pointer *p_state);
+void tvg_svg_in_ot_free(FT_Pointer *p_state);
+FT_Error tvg_svg_in_ot_preset_slot(FT_GlyphSlot p_slot, FT_Bool p_cache, FT_Pointer *p_state);
+FT_Error tvg_svg_in_ot_render(FT_GlyphSlot p_slot, FT_Pointer *p_state);
+
+SVG_RendererHooks *get_tvg_svg_in_ot_hooks();
+
+#endif // MODULE_SVG_ENABLED
+
+#endif // THORVG_SVG_IN_OT_H
diff --git a/modules/text_server_fb/SCsub b/modules/text_server_fb/SCsub
index 429d2e1fdc..f1d57ec4d3 100644
--- a/modules/text_server_fb/SCsub
+++ b/modules/text_server_fb/SCsub
@@ -8,6 +8,9 @@ msdfgen_enabled = "msdfgen" in env.module_list
env_text_server_fb = env_modules.Clone()
+if "svg" in env.module_list:
+ env_text_server_fb.Prepend(CPPPATH=["#thirdparty/thorvg/inc", "#thirdparty/thorvg/src/lib"])
+
if env["builtin_msdfgen"] and msdfgen_enabled:
env_text_server_fb.Prepend(CPPPATH=["#thirdparty/msdfgen"])
diff --git a/modules/text_server_fb/gdextension_build/SConstruct b/modules/text_server_fb/gdextension_build/SConstruct
index 8ed8f61a43..7b4c548a21 100644
--- a/modules/text_server_fb/gdextension_build/SConstruct
+++ b/modules/text_server_fb/gdextension_build/SConstruct
@@ -22,6 +22,7 @@ opts = Variables([], ARGUMENTS)
opts.Add(BoolVariable("brotli_enabled", "Use Brotli library", True))
opts.Add(BoolVariable("freetype_enabled", "Use FreeType library", True))
opts.Add(BoolVariable("msdfgen_enabled", "Use MSDFgen library (require FreeType)", True))
+opts.Add(BoolVariable("thorvg_enabled", "Use ThorVG library (require FreeType)", True))
opts.Add(BoolVariable("verbose", "Enable verbose output for the compilation", False))
opts.Update(env)
@@ -29,6 +30,79 @@ opts.Update(env)
if not env["verbose"]:
methods.no_verbose(sys, env)
+# ThorVG
+if env["thorvg_enabled"] and env["freetype_enabled"]:
+ env_tvg = env.Clone()
+ env_tvg.disable_warnings()
+
+ thirdparty_tvg_dir = "../../../thirdparty/thorvg/"
+ thirdparty_tvg_sources = [
+ "src/lib/sw_engine/tvgSwFill.cpp",
+ "src/lib/sw_engine/tvgSwImage.cpp",
+ "src/lib/sw_engine/tvgSwMath.cpp",
+ "src/lib/sw_engine/tvgSwMemPool.cpp",
+ "src/lib/sw_engine/tvgSwRaster.cpp",
+ "src/lib/sw_engine/tvgSwRenderer.cpp",
+ "src/lib/sw_engine/tvgSwRle.cpp",
+ "src/lib/sw_engine/tvgSwShape.cpp",
+ "src/lib/sw_engine/tvgSwStroke.cpp",
+ "src/lib/tvgAccessor.cpp",
+ "src/lib/tvgBezier.cpp",
+ "src/lib/tvgCanvas.cpp",
+ "src/lib/tvgFill.cpp",
+ "src/lib/tvgGlCanvas.cpp",
+ "src/lib/tvgInitializer.cpp",
+ "src/lib/tvgLinearGradient.cpp",
+ "src/lib/tvgLoader.cpp",
+ "src/lib/tvgLzw.cpp",
+ "src/lib/tvgPaint.cpp",
+ "src/lib/tvgPicture.cpp",
+ "src/lib/tvgRadialGradient.cpp",
+ "src/lib/tvgRender.cpp",
+ "src/lib/tvgSaver.cpp",
+ "src/lib/tvgScene.cpp",
+ "src/lib/tvgShape.cpp",
+ "src/lib/tvgSwCanvas.cpp",
+ "src/lib/tvgTaskScheduler.cpp",
+ "src/loaders/external_png/tvgPngLoader.cpp",
+ "src/loaders/jpg/tvgJpgd.cpp",
+ "src/loaders/jpg/tvgJpgLoader.cpp",
+ "src/loaders/raw/tvgRawLoader.cpp",
+ "src/loaders/svg/tvgSvgCssStyle.cpp",
+ "src/loaders/svg/tvgSvgLoader.cpp",
+ "src/loaders/svg/tvgSvgPath.cpp",
+ "src/loaders/svg/tvgSvgSceneBuilder.cpp",
+ "src/loaders/svg/tvgSvgUtil.cpp",
+ "src/loaders/svg/tvgXmlParser.cpp",
+ "src/loaders/tvg/tvgTvgBinInterpreter.cpp",
+ "src/loaders/tvg/tvgTvgLoader.cpp",
+ "src/savers/tvg/tvgTvgSaver.cpp",
+ ]
+ thirdparty_tvg_sources = [thirdparty_tvg_dir + file for file in thirdparty_tvg_sources]
+
+ env_tvg.Append(
+ CPPPATH=[
+ "../../../thirdparty/thorvg/inc",
+ "../../../thirdparty/thorvg/src/lib",
+ "../../../thirdparty/thorvg/src/lib/sw_engine",
+ "../../../thirdparty/thorvg/src/loaders/external_png",
+ "../../../thirdparty/thorvg/src/loaders/jpg",
+ "../../../thirdparty/thorvg/src/loaders/raw",
+ "../../../thirdparty/thorvg/src/loaders/svg",
+ "../../../thirdparty/thorvg/src/loaders/tvg",
+ "../../../thirdparty/thorvg/src/savers/tvg",
+ "../../../thirdparty/libpng",
+ ]
+ )
+ env.Append(CPPPATH=["../../../thirdparty/thorvg/inc", "../../../thirdparty/thorvg/src/lib"])
+ env.Append(CPPDEFINES=["MODULE_SVG_ENABLED"])
+
+ lib = env_tvg.Library(
+ f'tvg_builtin{env["suffix"]}{env["LIBSUFFIX"]}',
+ thirdparty_tvg_sources,
+ )
+ env.Append(LIBS=[lib])
+
# MSDFGEN
if env["msdfgen_enabled"] and env["freetype_enabled"]:
env_msdfgen = env.Clone()
diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp
index 999870b904..aaef9c9a3d 100644
--- a/modules/text_server_fb/text_server_fb.cpp
+++ b/modules/text_server_fb/text_server_fb.cpp
@@ -41,6 +41,8 @@
using namespace godot;
+#define GLOBAL_GET(m_var) ProjectSettings::get_singleton()->get(m_var)
+
#else
// Headers for building as built-in module.
@@ -49,7 +51,7 @@ using namespace godot;
#include "core/string/print_string.h"
#include "core/string/ucaps.h"
-#include "modules/modules_enabled.gen.h" // For freetype, msdfgen.
+#include "modules/modules_enabled.gen.h" // For freetype, msdfgen, svg.
#endif
@@ -62,6 +64,10 @@ using namespace godot;
#include "msdfgen.h"
#endif
+#ifdef MODULE_SVG_ENABLED
+#include "thorvg_svg_in_ot.h"
+#endif
+
/*************************************************************************/
#define OT_TAG(c1, c2, c3, c4) ((int32_t)((((uint32_t)(c1)&0xff) << 24) | (((uint32_t)(c2)&0xff) << 16) | (((uint32_t)(c3)&0xff) << 8) | ((uint32_t)(c4)&0xff)))
@@ -771,6 +777,9 @@ _FORCE_INLINE_ bool TextServerFallback::_ensure_cache_for_size(FontFallback *p_f
memdelete(fd);
ERR_FAIL_V_MSG(false, "FreeType: Error initializing library: '" + String(FT_Error_String(error)) + "'.");
}
+#ifdef MODULE_SVG_ENABLED
+ FT_Property_Set(ft_library, "ot-svg", "svg-hooks", get_tvg_svg_in_ot_hooks());
+#endif
}
memset(&fd->stream, 0, sizeof(FT_StreamRec));
@@ -992,6 +1001,9 @@ int64_t TextServerFallback::_font_get_face_count(const RID &p_font_rid) const {
if (!ft_library) {
error = FT_Init_FreeType(&ft_library);
ERR_FAIL_COND_V_MSG(error != 0, false, "FreeType: Error initializing library: '" + String(FT_Error_String(error)) + "'.");
+#ifdef MODULE_SVG_ENABLED
+ FT_Property_Set(ft_library, "ot-svg", "svg-hooks", get_tvg_svg_in_ot_hooks());
+#endif
}
FT_StreamRec stream;
diff --git a/modules/text_server_fb/text_server_fb.h b/modules/text_server_fb/text_server_fb.h
index 151ae13904..7e0bc99618 100644
--- a/modules/text_server_fb/text_server_fb.h
+++ b/modules/text_server_fb/text_server_fb.h
@@ -87,7 +87,7 @@ using namespace godot;
#include "core/templates/rid_owner.h"
#include "scene/resources/texture.h"
-#include "modules/modules_enabled.gen.h" // For freetype, msdfgen.
+#include "modules/modules_enabled.gen.h" // For freetype, msdfgen, svg.
#endif
@@ -101,6 +101,7 @@ using namespace godot;
#include FT_ADVANCES_H
#include FT_MULTIPLE_MASTERS_H
#include FT_BBOX_H
+#include FT_MODULE_H
#include FT_CONFIG_OPTIONS_H
#if !defined(FT_CONFIG_OPTION_USE_BROTLI) && !defined(_MSC_VER)
#warning FreeType is configured without Brotli support, built-in fonts will not be available.
diff --git a/modules/text_server_fb/thorvg_bounds_iterator.cpp b/modules/text_server_fb/thorvg_bounds_iterator.cpp
new file mode 100644
index 0000000000..54a6136134
--- /dev/null
+++ b/modules/text_server_fb/thorvg_bounds_iterator.cpp
@@ -0,0 +1,70 @@
+/*************************************************************************/
+/* thorvg_bounds_iterator.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifdef GDEXTENSION
+// Headers for building as GDExtension plug-in.
+
+#include <godot_cpp/godot.hpp>
+
+using namespace godot;
+
+#else
+// Headers for building as built-in module.
+
+#include "core/typedefs.h"
+
+#include "modules/modules_enabled.gen.h" // For svg.
+#endif
+
+#ifdef MODULE_SVG_ENABLED
+
+#include "thorvg_bounds_iterator.h"
+
+#include <tvgIteratorAccessor.h>
+#include <tvgPaint.h>
+
+// This function uses private ThorVG API to get bounding box of top level children elements.
+
+void tvg_get_bounds(tvg::Picture *p_picture, float &r_min_x, float &r_min_y, float &r_max_x, float &r_max_y) {
+ tvg::IteratorAccessor itrAccessor;
+ if (tvg::Iterator *it = itrAccessor.iterator(p_picture)) {
+ while (const tvg::Paint *child = it->next()) {
+ float x = 0, y = 0, w = 0, h = 0;
+ child->bounds(&x, &y, &w, &h, true);
+ r_min_x = MIN(x, r_min_x);
+ r_min_y = MIN(y, r_min_y);
+ r_max_x = MAX(x + w, r_max_x);
+ r_max_y = MAX(y + h, r_max_y);
+ }
+ delete (it);
+ }
+}
+
+#endif // MODULE_SVG_ENABLED
diff --git a/modules/text_server_fb/thorvg_bounds_iterator.h b/modules/text_server_fb/thorvg_bounds_iterator.h
new file mode 100644
index 0000000000..e54e30eaa2
--- /dev/null
+++ b/modules/text_server_fb/thorvg_bounds_iterator.h
@@ -0,0 +1,58 @@
+/*************************************************************************/
+/* thorvg_bounds_iterator.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef THORVG_BOUNDS_ITERATOR_H
+#define THORVG_BOUNDS_ITERATOR_H
+
+#ifdef GDEXTENSION
+// Headers for building as GDExtension plug-in.
+
+#include <godot_cpp/core/mutex_lock.hpp>
+#include <godot_cpp/godot.hpp>
+
+using namespace godot;
+
+#else
+// Headers for building as built-in module.
+
+#include "core/typedefs.h"
+
+#include "modules/modules_enabled.gen.h" // For svg.
+#endif
+
+#ifdef MODULE_SVG_ENABLED
+
+#include <thorvg.h>
+
+void tvg_get_bounds(tvg::Picture *p_picture, float &r_min_x, float &r_min_y, float &r_max_x, float &r_max_y);
+
+#endif // MODULE_SVG_ENABLED
+
+#endif // THORVG_BOUNDS_ITERATOR_H
diff --git a/modules/text_server_fb/thorvg_svg_in_ot.cpp b/modules/text_server_fb/thorvg_svg_in_ot.cpp
new file mode 100644
index 0000000000..7863ab67fa
--- /dev/null
+++ b/modules/text_server_fb/thorvg_svg_in_ot.cpp
@@ -0,0 +1,320 @@
+/*************************************************************************/
+/* thorvg_svg_in_ot.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifdef GDEXTENSION
+// Headers for building as GDExtension plug-in.
+
+#include <godot_cpp/classes/xml_parser.hpp>
+#include <godot_cpp/core/mutex_lock.hpp>
+#include <godot_cpp/godot.hpp>
+#include <godot_cpp/templates/vector.hpp>
+
+using namespace godot;
+
+#else
+// Headers for building as built-in module.
+
+#include "core/error/error_macros.h"
+#include "core/io/xml_parser.h"
+#include "core/os/memory.h"
+#include "core/os/os.h"
+#include "core/string/ustring.h"
+#include "core/typedefs.h"
+#include "core/variant/variant.h"
+
+#include "modules/modules_enabled.gen.h" // For svg.
+#endif
+
+#ifdef MODULE_SVG_ENABLED
+
+#include "thorvg_bounds_iterator.h"
+#include "thorvg_svg_in_ot.h"
+
+#include <freetype/otsvg.h>
+#include <ft2build.h>
+
+#include <math.h>
+#include <stdlib.h>
+
+FT_Error tvg_svg_in_ot_init(FT_Pointer *p_state) {
+ *p_state = memnew(TVG_State);
+
+ return FT_Err_Ok;
+}
+
+void tvg_svg_in_ot_free(FT_Pointer *p_state) {
+ TVG_State *state = *reinterpret_cast<TVG_State **>(p_state);
+ memdelete(state);
+}
+
+FT_Error tvg_svg_in_ot_preset_slot(FT_GlyphSlot p_slot, FT_Bool p_cache, FT_Pointer *p_state) {
+ TVG_State *state = *reinterpret_cast<TVG_State **>(p_state);
+ if (!state) {
+ ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "SVG in OT state not initialized.");
+ }
+ MutexLock lock(state->mutex);
+
+ FT_SVG_Document document = (FT_SVG_Document)p_slot->other;
+ FT_Size_Metrics metrics = document->metrics;
+
+ GL_State &gl_state = state->glyph_map[p_slot->glyph_index];
+ if (!gl_state.ready) {
+ Ref<XMLParser> parser;
+ parser.instantiate();
+#ifdef GDEXTENSION
+ PackedByteArray data;
+ data.resize(document->svg_document_length);
+ memcpy(data.ptrw(), document->svg_document, document->svg_document_length);
+ parser->open_buffer(data);
+#else
+ parser->_open_buffer((const uint8_t *)document->svg_document, document->svg_document_length);
+#endif
+
+ float aspect = 1.0f;
+ String xml_body;
+ while (parser->read() == OK) {
+ if (parser->has_attribute("id")) {
+#ifdef GDEXTENSION
+ const String &gl_name = parser->get_named_attribute_value("id");
+#else
+ const String &gl_name = parser->get_attribute_value("id");
+#endif
+ if (gl_name.begins_with("glyph")) {
+ int dot_pos = gl_name.find(".");
+ int64_t gl_idx = gl_name.substr(5, (dot_pos > 0) ? dot_pos - 5 : -1).to_int();
+ if (p_slot->glyph_index != gl_idx) {
+ parser->skip_section();
+ continue;
+ }
+ }
+ }
+ if (parser->get_node_type() == XMLParser::NODE_ELEMENT && parser->get_node_name() == "svg") {
+ if (parser->has_attribute("viewBox")) {
+#ifdef GDEXTENSION
+ PackedStringArray vb = parser->get_named_attribute_value("viewBox").split(" ");
+#else
+ Vector<String> vb = parser->get_attribute_value("viewBox").split(" ");
+#endif
+
+ if (vb.size() == 4) {
+ aspect = vb[2].to_float() / vb[3].to_float();
+ }
+ }
+ continue;
+ }
+#ifdef GDEXTENSION
+ if (parser->get_node_type() == XMLParser::NODE_ELEMENT) {
+ xml_body = xml_body + "<" + parser->get_node_name();
+ for (int i = 0; i < parser->get_attribute_count(); i++) {
+ xml_body = xml_body + " " + parser->get_attribute_name(i) + "=\"" + parser->get_attribute_value(i) + "\"";
+ }
+ xml_body = xml_body + ">";
+ } else if (parser->get_node_type() == XMLParser::NODE_TEXT) {
+ xml_body = xml_body + parser->get_node_data();
+ } else if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END) {
+ xml_body = xml_body + "</" + parser->get_node_name() + ">";
+ }
+#else
+ if (parser->get_node_type() == XMLParser::NODE_ELEMENT) {
+ xml_body += vformat("<%s", parser->get_node_name());
+ for (int i = 0; i < parser->get_attribute_count(); i++) {
+ xml_body += vformat(" %s=\"%s\"", parser->get_attribute_name(i), parser->get_attribute_value(i));
+ }
+ xml_body += ">";
+ } else if (parser->get_node_type() == XMLParser::NODE_TEXT) {
+ xml_body += parser->get_node_data();
+ } else if (parser->get_node_type() == XMLParser::NODE_ELEMENT_END) {
+ xml_body += vformat("</%s>", parser->get_node_name());
+ }
+#endif
+ }
+ String temp_xml = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 0 0\">" + xml_body;
+
+ std::unique_ptr<tvg::Picture> picture = tvg::Picture::gen();
+ tvg::Result result = picture->load(temp_xml.utf8().get_data(), temp_xml.utf8().length(), "svg+xml", false);
+ if (result != tvg::Result::Success) {
+ ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to load SVG document (bounds detection).");
+ }
+
+ float min_x = INFINITY, min_y = INFINITY, max_x = -INFINITY, max_y = -INFINITY;
+ tvg_get_bounds(picture.get(), min_x, min_y, max_x, max_y);
+
+ float new_h = (max_y - min_y);
+ float new_w = (max_x - min_x);
+
+ if (new_h * aspect >= new_w) {
+ new_w = (new_h * aspect);
+ } else {
+ new_h = (new_w / aspect);
+ }
+
+#ifdef GDEXTENSION
+ gl_state.xml_code = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"" + rtos(min_x) + " " + rtos(min_y) + " " + rtos(new_w) + " " + rtos(new_h) + "\">" + xml_body;
+#else
+ gl_state.xml_code = vformat("<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"%f %f %f %f\">", min_x, min_y, new_w, new_h) + xml_body;
+#endif
+
+ picture = tvg::Picture::gen();
+ result = picture->load(gl_state.xml_code.utf8().get_data(), gl_state.xml_code.utf8().length(), "svg+xml", false);
+ if (result != tvg::Result::Success) {
+ ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to load SVG document (glyph metrics).");
+ }
+
+ float x_svg_to_out, y_svg_to_out;
+ x_svg_to_out = (float)metrics.x_ppem / new_w;
+ y_svg_to_out = (float)metrics.y_ppem / new_h;
+
+ gl_state.m.e11 = (double)document->transform.xx / (1 << 16) * x_svg_to_out;
+ gl_state.m.e12 = -(double)document->transform.xy / (1 << 16) * x_svg_to_out;
+ gl_state.m.e21 = -(double)document->transform.yx / (1 << 16) * y_svg_to_out;
+ gl_state.m.e22 = (double)document->transform.yy / (1 << 16) * y_svg_to_out;
+ gl_state.m.e13 = (double)document->delta.x / 64 * new_w / metrics.x_ppem;
+ gl_state.m.e23 = -(double)document->delta.y / 64 * new_h / metrics.y_ppem;
+ gl_state.m.e31 = 0;
+ gl_state.m.e32 = 0;
+ gl_state.m.e33 = 1;
+
+ result = picture->transform(gl_state.m);
+ if (result != tvg::Result::Success) {
+ ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to apply transform to SVG document.");
+ }
+
+ result = picture->bounds(&gl_state.x, &gl_state.y, &gl_state.w, &gl_state.h, true);
+ if (result != tvg::Result::Success) {
+ ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to get SVG bounds.");
+ }
+
+ gl_state.bmp_y = -min_y * gl_state.h / new_h;
+ gl_state.bmp_x = min_x * gl_state.w / new_w;
+
+ gl_state.ready = true;
+ }
+
+ p_slot->bitmap_left = (FT_Int)gl_state.bmp_x;
+ p_slot->bitmap_top = (FT_Int)gl_state.bmp_y;
+
+ float tmp = ceil(gl_state.h);
+ p_slot->bitmap.rows = (unsigned int)tmp;
+ tmp = ceil(gl_state.w);
+ p_slot->bitmap.width = (unsigned int)tmp;
+ p_slot->bitmap.pitch = (int)p_slot->bitmap.width * 4;
+ p_slot->bitmap.pixel_mode = FT_PIXEL_MODE_BGRA;
+
+ float metrics_width, metrics_height;
+ float horiBearingX, horiBearingY;
+ float vertBearingX, vertBearingY;
+
+ metrics_width = (float)gl_state.w;
+ metrics_height = (float)gl_state.h;
+ horiBearingX = (float)gl_state.x;
+ horiBearingY = (float)-gl_state.y;
+ vertBearingX = p_slot->metrics.horiBearingX / 64.0f - p_slot->metrics.horiAdvance / 64.0f / 2;
+ vertBearingY = (p_slot->metrics.vertAdvance / 64.0f - p_slot->metrics.height / 64.0f) / 2;
+
+ tmp = roundf(metrics_width * 64);
+ p_slot->metrics.width = (FT_Pos)tmp;
+ tmp = roundf(metrics_height * 64);
+ p_slot->metrics.height = (FT_Pos)tmp;
+
+ p_slot->metrics.horiBearingX = (FT_Pos)(horiBearingX * 64);
+ p_slot->metrics.horiBearingY = (FT_Pos)(horiBearingY * 64);
+ p_slot->metrics.vertBearingX = (FT_Pos)(vertBearingX * 64);
+ p_slot->metrics.vertBearingY = (FT_Pos)(vertBearingY * 64);
+
+ if (p_slot->metrics.vertAdvance == 0) {
+ p_slot->metrics.vertAdvance = (FT_Pos)(metrics_height * 1.2f * 64);
+ }
+
+ return FT_Err_Ok;
+}
+
+FT_Error tvg_svg_in_ot_render(FT_GlyphSlot p_slot, FT_Pointer *p_state) {
+ TVG_State *state = *reinterpret_cast<TVG_State **>(p_state);
+ if (!state) {
+ ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "SVG in OT state not initialized.");
+ }
+ MutexLock lock(state->mutex);
+
+ if (!state->glyph_map.has(p_slot->glyph_index)) {
+ ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "SVG glyph not loaded.");
+ }
+
+ GL_State &gl_state = state->glyph_map[p_slot->glyph_index];
+ ERR_FAIL_COND_V_MSG(!gl_state.ready, FT_Err_Invalid_SVG_Document, "SVG glyph not ready.");
+
+ std::unique_ptr<tvg::Picture> picture = tvg::Picture::gen();
+ tvg::Result res = picture->load(gl_state.xml_code.utf8().get_data(), gl_state.xml_code.utf8().length(), "svg+xml", false);
+ if (res != tvg::Result::Success) {
+ ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to load SVG document (glyph rendering).");
+ }
+ res = picture->transform(gl_state.m);
+ if (res != tvg::Result::Success) {
+ ERR_FAIL_V_MSG(FT_Err_Invalid_SVG_Document, "Failed to apply transform to SVG document.");
+ }
+
+ std::unique_ptr<tvg::SwCanvas> sw_canvas = tvg::SwCanvas::gen();
+ res = sw_canvas->target((uint32_t *)p_slot->bitmap.buffer, (int)p_slot->bitmap.width, (int)p_slot->bitmap.width, (int)p_slot->bitmap.rows, tvg::SwCanvas::ARGB8888_STRAIGHT);
+ if (res != tvg::Result::Success) {
+ ERR_FAIL_V_MSG(FT_Err_Invalid_Outline, "Failed to create SVG canvas.");
+ }
+ res = sw_canvas->push(std::move(picture));
+ if (res != tvg::Result::Success) {
+ ERR_FAIL_V_MSG(FT_Err_Invalid_Outline, "Failed to set SVG canvas source.");
+ }
+ res = sw_canvas->draw();
+ if (res != tvg::Result::Success) {
+ ERR_FAIL_V_MSG(FT_Err_Invalid_Outline, "Failed to draw to SVG canvas.");
+ }
+ res = sw_canvas->sync();
+ if (res != tvg::Result::Success) {
+ ERR_FAIL_V_MSG(FT_Err_Invalid_Outline, "Failed to sync SVG canvas.");
+ }
+
+ state->glyph_map.erase(p_slot->glyph_index);
+
+ p_slot->bitmap.pixel_mode = FT_PIXEL_MODE_BGRA;
+ p_slot->bitmap.num_grays = 256;
+ p_slot->format = FT_GLYPH_FORMAT_BITMAP;
+
+ return FT_Err_Ok;
+}
+
+SVG_RendererHooks tvg_svg_in_ot_hooks = {
+ (SVG_Lib_Init_Func)tvg_svg_in_ot_init,
+ (SVG_Lib_Free_Func)tvg_svg_in_ot_free,
+ (SVG_Lib_Render_Func)tvg_svg_in_ot_render,
+ (SVG_Lib_Preset_Slot_Func)tvg_svg_in_ot_preset_slot,
+};
+
+SVG_RendererHooks *get_tvg_svg_in_ot_hooks() {
+ return &tvg_svg_in_ot_hooks;
+}
+
+#endif // MODULE_SVG_ENABLED
diff --git a/modules/text_server_fb/thorvg_svg_in_ot.h b/modules/text_server_fb/thorvg_svg_in_ot.h
new file mode 100644
index 0000000000..b2816193d9
--- /dev/null
+++ b/modules/text_server_fb/thorvg_svg_in_ot.h
@@ -0,0 +1,86 @@
+/*************************************************************************/
+/* thorvg_svg_in_ot.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef THORVG_SVG_IN_OT_H
+#define THORVG_SVG_IN_OT_H
+
+#ifdef GDEXTENSION
+// Headers for building as GDExtension plug-in.
+
+#include <godot_cpp/core/mutex_lock.hpp>
+#include <godot_cpp/godot.hpp>
+#include <godot_cpp/templates/hash_map.hpp>
+
+using namespace godot;
+
+#else
+// Headers for building as built-in module.
+
+#include "core/os/mutex.h"
+#include "core/templates/hash_map.h"
+#include "core/typedefs.h"
+
+#include "modules/modules_enabled.gen.h" // For svg.
+#endif
+
+#ifdef MODULE_SVG_ENABLED
+
+#include <freetype/freetype.h>
+#include <freetype/otsvg.h>
+#include <ft2build.h>
+#include <thorvg.h>
+
+struct GL_State {
+ bool ready = false;
+ float bmp_x = 0;
+ float bmp_y = 0;
+ float x = 0;
+ float y = 0;
+ float w = 0;
+ float h = 0;
+ String xml_code;
+ tvg::Matrix m;
+};
+
+struct TVG_State {
+ Mutex mutex;
+ HashMap<uint32_t, GL_State> glyph_map;
+};
+
+FT_Error tvg_svg_in_ot_init(FT_Pointer *p_state);
+void tvg_svg_in_ot_free(FT_Pointer *p_state);
+FT_Error tvg_svg_in_ot_preset_slot(FT_GlyphSlot p_slot, FT_Bool p_cache, FT_Pointer *p_state);
+FT_Error tvg_svg_in_ot_render(FT_GlyphSlot p_slot, FT_Pointer *p_state);
+
+SVG_RendererHooks *get_tvg_svg_in_ot_hooks();
+
+#endif // MODULE_SVG_ENABLED
+
+#endif // THORVG_SVG_IN_OT_H
diff --git a/modules/zip/zip_packer.cpp b/modules/zip/zip_packer.cpp
index e62700f191..5566848087 100644
--- a/modules/zip/zip_packer.cpp
+++ b/modules/zip/zip_packer.cpp
@@ -46,7 +46,11 @@ Error ZIPPacker::open(String p_path, ZipAppend p_append) {
Error ZIPPacker::close() {
ERR_FAIL_COND_V_MSG(fa.is_null(), FAILED, "ZIPPacker cannot be closed because it is not open.");
- return zipClose(zf, NULL) == ZIP_OK ? OK : FAILED;
+ Error err = zipClose(zf, NULL) == ZIP_OK ? OK : FAILED;
+ if (err == OK) {
+ zf = NULL;
+ }
+ return err;
}
Error ZIPPacker::start_file(String p_path) {
@@ -79,11 +83,7 @@ Error ZIPPacker::write_file(Vector<uint8_t> p_data) {
Error ZIPPacker::close_file() {
ERR_FAIL_COND_V_MSG(fa.is_null(), FAILED, "ZIPPacker must be opened before use.");
- Error err = zipCloseFileInZip(zf) == ZIP_OK ? OK : FAILED;
- if (err == OK) {
- zf = NULL;
- }
- return err;
+ return zipCloseFileInZip(zf) == ZIP_OK ? OK : FAILED;
}
void ZIPPacker::_bind_methods() {