Prechádzať zdrojové kódy

Single Compilation Unit build.

Adds support for simple SCU build (DEV_ENABLED only).
This speeds up compilation by compiling multiple cpp files within a single translation unit.
lawnjelly 2 rokov pred
rodič
commit
b69c8b4791

+ 12 - 0
SConstruct

@@ -55,6 +55,7 @@ _helper_module("modules.modules_builders", "modules/modules_builders.py")
 import methods
 import glsl_builders
 import gles3_builders
+import scu_builders
 from platform_methods import architectures, architecture_aliases
 
 if ARGUMENTS.get("target", "editor") == "editor":
@@ -223,6 +224,7 @@ opts.Add(
     "",
 )
 opts.Add(BoolVariable("use_precise_math_checks", "Math checks use very precise epsilon (debug option)", False))
+opts.Add(EnumVariable("scu_build", "Use single compilation unit build", "none", ("none", "dev", "all")))
 
 # Thirdparty libraries
 opts.Add(BoolVariable("builtin_certs", "Use the built-in SSL certificates bundles", True))
@@ -428,14 +430,20 @@ if env_base.debug_features:
     # to give *users* extra debugging information for their game development.
     env_base.Append(CPPDEFINES=["DEBUG_ENABLED"])
 
+
 if env_base.dev_build:
     # DEV_ENABLED enables *engine developer* code which should only be compiled for those
     # working on the engine itself.
     env_base.Append(CPPDEFINES=["DEV_ENABLED"])
+    env_base["use_scu"] = env_base["scu_build"] in ("dev", "all")
 else:
     # Disable assert() for production targets (only used in thirdparty code).
     env_base.Append(CPPDEFINES=["NDEBUG"])
 
+    # SCU builds currently use a lot of compiler memory
+    # in release builds, so disallow outside of DEV builds unless "all" is set.
+    env_base["use_scu"] = env_base["scu_build"] == "all"
+
 # SCons speed optimization controlled by the `fast_unsafe` option, which provide
 # more than 10 s speed up for incremental rebuilds.
 # Unsafe as they reduce the certainty of rebuilding all changed files, so it's
@@ -550,6 +558,10 @@ if selected_platform in platform_list:
         # LTO "auto" means we handle the preferred option in each platform detect.py.
         env["lto"] = ARGUMENTS.get("lto", "auto")
 
+    # Run SCU file generation script if in a SCU build.
+    if env["use_scu"]:
+        methods.set_scu_folders(scu_builders.generate_scu_files(env["verbose"], env_base.dev_build == False))
+
     # Must happen after the flags' definition, as configure is when most flags
     # are actually handled to change compile options, etc.
     detect.configure(env)

+ 2 - 3
editor/SCsub

@@ -29,9 +29,8 @@ if env.editor_build:
     reg_exporters_inc = '#include "register_exporters.h"\n\n'
     reg_exporters = "void register_exporters() {\n"
     for e in env.platform_exporters:
-        # Glob all .cpp files in export folder
-        files = Glob("#platform/" + e + "/export/" + "*.cpp")
-        env.add_source_files(env.editor_sources, files)
+        # Add all .cpp files in export folder
+        env.add_source_files(env.editor_sources, "../platform/" + e + "/export/" + "*.cpp")
 
         reg_exporters += "\tregister_" + e + "_exporter();\n"
         reg_exporters_inc += '#include "platform/' + e + '/export/export.h"\n'

+ 1 - 1
editor/editor_about.cpp

@@ -36,7 +36,7 @@
 #include "core/version.h"
 
 // The metadata key used to store and retrieve the version text to copy to the clipboard.
-static const String META_TEXT_TO_COPY = "text_to_copy";
+const String EditorAbout::META_TEXT_TO_COPY = "text_to_copy";
 
 void EditorAbout::_theme_changed() {
 	const Ref<Font> font = get_theme_font(SNAME("source"), SNAME("EditorFonts"));

+ 2 - 0
editor/editor_about.h

@@ -51,6 +51,8 @@
 class EditorAbout : public AcceptDialog {
 	GDCLASS(EditorAbout, AcceptDialog);
 
+	static const String META_TEXT_TO_COPY;
+
 private:
 	void _license_tree_selected();
 	void _version_button_pressed();

+ 4 - 4
editor/editor_inspector.cpp

@@ -46,14 +46,14 @@
 #include "scene/property_utils.h"
 #include "scene/resources/packed_scene.h"
 
-static bool _property_path_matches(const String &p_property_path, const String &p_filter, EditorPropertyNameProcessor::Style p_style) {
+bool EditorInspector::_property_path_matches(const String &p_property_path, const String &p_filter, EditorPropertyNameProcessor::Style p_style) {
 	if (p_property_path.findn(p_filter) != -1) {
 		return true;
 	}
 
-	const Vector<String> sections = p_property_path.split("/");
-	for (int i = 0; i < sections.size(); i++) {
-		if (p_filter.is_subsequence_ofn(EditorPropertyNameProcessor::get_singleton()->process_name(sections[i], p_style))) {
+	const Vector<String> prop_sections = p_property_path.split("/");
+	for (int i = 0; i < prop_sections.size(); i++) {
+		if (p_filter.is_subsequence_ofn(EditorPropertyNameProcessor::get_singleton()->process_name(prop_sections[i], p_style))) {
 			return true;
 		}
 	}

+ 1 - 0
editor/editor_inspector.h

@@ -512,6 +512,7 @@ class EditorInspector : public ScrollContainer {
 	void _property_deleted(const String &p_path);
 	void _property_checked(const String &p_path, bool p_checked);
 	void _property_pinned(const String &p_path, bool p_pinned);
+	bool _property_path_matches(const String &p_property_path, const String &p_filter, EditorPropertyNameProcessor::Style p_style);
 
 	void _resource_selected(const String &p_path, Ref<Resource> p_resource);
 	void _property_selected(const String &p_path, int p_focusable);

+ 8 - 8
editor/editor_node.cpp

@@ -6760,28 +6760,28 @@ EditorNode::EditorNode() {
 		switch (display_scale) {
 			case 0:
 				// Try applying a suitable display scale automatically.
-				editor_set_scale(EditorSettings::get_singleton()->get_auto_display_scale());
+				EditorScale::set_scale(EditorSettings::get_singleton()->get_auto_display_scale());
 				break;
 			case 1:
-				editor_set_scale(0.75);
+				EditorScale::set_scale(0.75);
 				break;
 			case 2:
-				editor_set_scale(1.0);
+				EditorScale::set_scale(1.0);
 				break;
 			case 3:
-				editor_set_scale(1.25);
+				EditorScale::set_scale(1.25);
 				break;
 			case 4:
-				editor_set_scale(1.5);
+				EditorScale::set_scale(1.5);
 				break;
 			case 5:
-				editor_set_scale(1.75);
+				EditorScale::set_scale(1.75);
 				break;
 			case 6:
-				editor_set_scale(2.0);
+				EditorScale::set_scale(2.0);
 				break;
 			default:
-				editor_set_scale(EDITOR_GET("interface/editor/custom_display_scale"));
+				EditorScale::set_scale(EDITOR_GET("interface/editor/custom_display_scale"));
 				break;
 		}
 	}

+ 2 - 2
editor/editor_quick_open.cpp

@@ -34,8 +34,8 @@
 #include "editor/editor_node.h"
 #include "editor/editor_scale.h"
 
-static Rect2i prev_rect = Rect2i();
-static bool was_showed = false;
+Rect2i EditorQuickOpen::prev_rect = Rect2i();
+bool EditorQuickOpen::was_showed = false;
 
 void EditorQuickOpen::popup_dialog(const String &p_base, bool p_enable_multi, bool p_dont_clear) {
 	base_type = p_base;

+ 3 - 0
editor/editor_quick_open.h

@@ -39,6 +39,9 @@
 class EditorQuickOpen : public ConfirmationDialog {
 	GDCLASS(EditorQuickOpen, ConfirmationDialog);
 
+	static Rect2i prev_rect;
+	static bool was_showed;
+
 	LineEdit *search_box = nullptr;
 	Tree *search_options = nullptr;
 	String base_type;

+ 5 - 7
editor/editor_scale.cpp

@@ -30,14 +30,12 @@
 
 #include "editor_scale.h"
 
-#include "core/os/os.h"
+float EditorScale::_scale = 1.0f;
 
-static float scale = 1.0;
-
-void editor_set_scale(float p_scale) {
-	scale = p_scale;
+void EditorScale::set_scale(float p_scale) {
+	_scale = p_scale;
 }
 
-float editor_get_scale() {
-	return scale;
+float EditorScale::get_scale() {
+	return _scale;
 }

+ 8 - 3
editor/editor_scale.h

@@ -31,9 +31,14 @@
 #ifndef EDITOR_SCALE_H
 #define EDITOR_SCALE_H
 
-void editor_set_scale(float p_scale);
-float editor_get_scale();
+class EditorScale {
+	static float _scale;
 
-#define EDSCALE (editor_get_scale())
+public:
+	static void set_scale(float p_scale);
+	static float get_scale();
+};
+
+#define EDSCALE (EditorScale::get_scale())
 
 #endif // EDITOR_SCALE_H

+ 1 - 1
editor/plugins/text_editor.cpp

@@ -481,7 +481,7 @@ void TextEditor::_convert_case(CodeTextEditor::CaseStyle p_case) {
 	code_editor->convert_case(p_case);
 }
 
-static ScriptEditorBase *create_editor(const Ref<Resource> &p_resource) {
+ScriptEditorBase *TextEditor::create_editor(const Ref<Resource> &p_resource) {
 	if (Object::cast_to<TextFile>(*p_resource) || Object::cast_to<JSON>(*p_resource)) {
 		return memnew(TextEditor);
 	}

+ 2 - 0
editor/plugins/text_editor.h

@@ -38,6 +38,8 @@
 class TextEditor : public ScriptEditorBase {
 	GDCLASS(TextEditor, ScriptEditorBase);
 
+	static ScriptEditorBase *create_editor(const Ref<Resource> &p_resource);
+
 private:
 	CodeTextEditor *code_editor = nullptr;
 

+ 2 - 2
editor/plugins/visual_shader_editor_plugin.cpp

@@ -4982,7 +4982,7 @@ void VisualShaderEditor::_preview_size_changed() {
 	preview_vbox->set_custom_minimum_size(preview_window->get_size());
 }
 
-static ShaderLanguage::DataType _get_global_shader_uniform_type(const StringName &p_variable) {
+static ShaderLanguage::DataType _visual_shader_editor_get_global_shader_uniform_type(const StringName &p_variable) {
 	RS::GlobalShaderParameterType gvt = RS::get_singleton()->global_shader_parameter_get_type(p_variable);
 	return (ShaderLanguage::DataType)RS::global_shader_uniform_type_get_shader_datatype(gvt);
 }
@@ -5001,7 +5001,7 @@ void VisualShaderEditor::_update_preview() {
 	info.functions = ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(visual_shader->get_mode()));
 	info.render_modes = ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(visual_shader->get_mode()));
 	info.shader_types = ShaderTypes::get_singleton()->get_types();
-	info.global_shader_uniform_type_func = _get_global_shader_uniform_type;
+	info.global_shader_uniform_type_func = _visual_shader_editor_get_global_shader_uniform_type;
 
 	ShaderLanguage sl;
 

+ 8 - 8
editor/project_manager.cpp

@@ -2763,28 +2763,28 @@ ProjectManager::ProjectManager() {
 		switch (display_scale) {
 			case 0:
 				// Try applying a suitable display scale automatically.
-				editor_set_scale(EditorSettings::get_singleton()->get_auto_display_scale());
+				EditorScale::set_scale(EditorSettings::get_singleton()->get_auto_display_scale());
 				break;
 			case 1:
-				editor_set_scale(0.75);
+				EditorScale::set_scale(0.75);
 				break;
 			case 2:
-				editor_set_scale(1.0);
+				EditorScale::set_scale(1.0);
 				break;
 			case 3:
-				editor_set_scale(1.25);
+				EditorScale::set_scale(1.25);
 				break;
 			case 4:
-				editor_set_scale(1.5);
+				EditorScale::set_scale(1.5);
 				break;
 			case 5:
-				editor_set_scale(1.75);
+				EditorScale::set_scale(1.75);
 				break;
 			case 6:
-				editor_set_scale(2.0);
+				EditorScale::set_scale(2.0);
 				break;
 			default:
-				editor_set_scale(EDITOR_GET("interface/editor/custom_display_scale"));
+				EditorScale::set_scale(EDITOR_GET("interface/editor/custom_display_scale"));
 				break;
 		}
 		EditorFileDialog::get_icon_func = &ProjectManager::_file_dialog_get_icon;

+ 82 - 2
methods.py

@@ -6,9 +6,23 @@ import subprocess
 from collections import OrderedDict
 from collections.abc import Mapping
 from typing import Iterator
+from pathlib import Path
+from os.path import normpath, basename
 
+# Get the "Godot" folder name ahead of time
+base_folder_path = str(os.path.abspath(Path(__file__).parent)) + "/"
+base_folder_only = os.path.basename(os.path.normpath(base_folder_path))
+# Listing all the folders we have converted
+# for SCU in scu_builders.py
+_scu_folders = set()
 
-def add_source_files(self, sources, files):
+
+def set_scu_folders(scu_folders):
+    global _scu_folders
+    _scu_folders = scu_folders
+
+
+def add_source_files_orig(self, sources, files, allow_gen=False):
     # Convert string to list of absolute paths (including expanding wildcard)
     if isinstance(files, (str, bytes)):
         # Keep SCons project-absolute path as they are (no wildcard support)
@@ -23,7 +37,7 @@ def add_source_files(self, sources, files):
             skip_gen_cpp = "*" in files
             dir_path = self.Dir(".").abspath
             files = sorted(glob.glob(dir_path + "/" + files))
-            if skip_gen_cpp:
+            if skip_gen_cpp and not allow_gen:
                 files = [f for f in files if not f.endswith(".gen.cpp")]
 
     # Add each path as compiled Object following environment (self) configuration
@@ -35,6 +49,72 @@ def add_source_files(self, sources, files):
         sources.append(obj)
 
 
+# The section name is used for checking
+# the hash table to see whether the folder
+# is included in the SCU build.
+# It will be something like "core/math".
+def _find_scu_section_name(subdir):
+    section_path = os.path.abspath(subdir) + "/"
+
+    folders = []
+    folder = ""
+
+    for i in range(8):
+        folder = os.path.dirname(section_path)
+        folder = os.path.basename(folder)
+        if folder == base_folder_only:
+            break
+        folders += [folder]
+        section_path += "../"
+        section_path = os.path.abspath(section_path) + "/"
+
+    section_name = ""
+    for n in range(len(folders)):
+        # section_name += folders[len(folders) - n - 1] + " "
+        section_name += folders[len(folders) - n - 1]
+        if n != (len(folders) - 1):
+            section_name += "/"
+
+    return section_name
+
+
+def add_source_files_scu(self, sources, files, allow_gen=False):
+    if self["use_scu"] and isinstance(files, str):
+        if "*." not in files:
+            return False
+
+        # If the files are in a subdirectory, we want to create the scu gen
+        # files inside this subdirectory.
+        subdir = os.path.dirname(files)
+        if subdir != "":
+            subdir += "/"
+
+        section_name = _find_scu_section_name(subdir)
+        # if the section name is in the hash table?
+        # i.e. is it part of the SCU build?
+        global _scu_folders
+        if section_name not in (_scu_folders):
+            return False
+
+        if self["verbose"]:
+            print("SCU building " + section_name)
+
+        # Add all the gen.cpp files in the SCU directory
+        add_source_files_orig(self, sources, subdir + "scu/scu_*.gen.cpp", True)
+        return True
+    return False
+
+
+# Either builds the folder using the SCU system,
+# or reverts to regular build.
+def add_source_files(self, sources, files, allow_gen=False):
+    if not add_source_files_scu(self, sources, files, allow_gen):
+        # Wraps the original function when scu build is not active.
+        add_source_files_orig(self, sources, files, allow_gen)
+        return False
+    return True
+
+
 def disable_warnings(self):
     # 'self' is the environment
     if self.msvc:

+ 1 - 1
scene/animation/animation_tree.cpp

@@ -941,7 +941,7 @@ void AnimationTree::_clear_playing_caches() {
 	playing_caches.clear();
 }
 
-static void _call_object(Object *p_object, const StringName &p_method, const Vector<Variant> &p_params, bool p_deferred) {
+void AnimationTree::_call_object(Object *p_object, const StringName &p_method, const Vector<Variant> &p_params, bool p_deferred) {
 	// Separate function to use alloca() more efficiently
 	const Variant **argptrs = (const Variant **)alloca(sizeof(const Variant **) * p_params.size());
 	const Variant *args = p_params.ptr();

+ 2 - 0
scene/animation/animation_tree.h

@@ -194,6 +194,8 @@ class AnimationNodeEndState : public AnimationRootNode {
 class AnimationTree : public Node {
 	GDCLASS(AnimationTree, Node);
 
+	void _call_object(Object *p_object, const StringName &p_method, const Vector<Variant> &p_params, bool p_deferred);
+
 public:
 	enum AnimationProcessCallback {
 		ANIMATION_PROCESS_PHYSICS,

+ 0 - 6
scene/gui/graph_node.cpp

@@ -34,12 +34,6 @@
 
 #include "graph_edit.h"
 
-struct _MinSizeCache {
-	int min_size;
-	bool will_stretch;
-	int final_size;
-};
-
 bool GraphNode::_set(const StringName &p_name, const Variant &p_value) {
 	String str = p_name;
 

+ 6 - 0
scene/gui/graph_node.h

@@ -37,6 +37,12 @@
 class GraphNode : public Container {
 	GDCLASS(GraphNode, Container);
 
+	struct _MinSizeCache {
+		int min_size;
+		bool will_stretch;
+		int final_size;
+	};
+
 public:
 	enum Overlay {
 		OVERLAY_DISABLED,

+ 2 - 2
scene/resources/surface_tool.cpp

@@ -795,8 +795,8 @@ void SurfaceTool::_create_list(const Ref<Mesh> &p_existing, int p_surface, Local
 	_create_list_from_arrays(arr, r_vertex, r_index, lformat);
 }
 
-static const uint32_t custom_mask[RS::ARRAY_CUSTOM_COUNT] = { Mesh::ARRAY_FORMAT_CUSTOM0, Mesh::ARRAY_FORMAT_CUSTOM1, Mesh::ARRAY_FORMAT_CUSTOM2, Mesh::ARRAY_FORMAT_CUSTOM3 };
-static const uint32_t custom_shift[RS::ARRAY_CUSTOM_COUNT] = { Mesh::ARRAY_FORMAT_CUSTOM0_SHIFT, Mesh::ARRAY_FORMAT_CUSTOM1_SHIFT, Mesh::ARRAY_FORMAT_CUSTOM2_SHIFT, Mesh::ARRAY_FORMAT_CUSTOM3_SHIFT };
+const uint32_t SurfaceTool::custom_mask[RS::ARRAY_CUSTOM_COUNT] = { Mesh::ARRAY_FORMAT_CUSTOM0, Mesh::ARRAY_FORMAT_CUSTOM1, Mesh::ARRAY_FORMAT_CUSTOM2, Mesh::ARRAY_FORMAT_CUSTOM3 };
+const uint32_t SurfaceTool::custom_shift[RS::ARRAY_CUSTOM_COUNT] = { Mesh::ARRAY_FORMAT_CUSTOM0_SHIFT, Mesh::ARRAY_FORMAT_CUSTOM1_SHIFT, Mesh::ARRAY_FORMAT_CUSTOM2_SHIFT, Mesh::ARRAY_FORMAT_CUSTOM3_SHIFT };
 
 void SurfaceTool::create_vertex_array_from_triangle_arrays(const Array &p_arrays, LocalVector<SurfaceTool::Vertex> &ret, uint32_t *r_format) {
 	ret.clear();

+ 3 - 0
scene/resources/surface_tool.h

@@ -38,6 +38,9 @@
 class SurfaceTool : public RefCounted {
 	GDCLASS(SurfaceTool, RefCounted);
 
+	static const uint32_t custom_mask[RS::ARRAY_CUSTOM_COUNT];
+	static const uint32_t custom_shift[RS::ARRAY_CUSTOM_COUNT];
+
 public:
 	struct Vertex {
 		Vector3 vertex;

+ 339 - 0
scu_builders.py

@@ -0,0 +1,339 @@
+"""Functions used to generate scu build source files during build time
+"""
+import glob, os
+import math
+from pathlib import Path
+from os.path import normpath, basename
+
+base_folder_path = str(Path(__file__).parent) + "/"
+base_folder_only = os.path.basename(os.path.normpath(base_folder_path))
+_verbose = False
+_is_release_build = False
+_scu_folders = set()
+
+
+def clear_out_existing_files(output_folder, extension):
+    output_folder = os.path.abspath(output_folder)
+    # print("clear_out_existing_files from folder: " + output_folder)
+
+    if not os.path.isdir(output_folder):
+        # folder does not exist or has not been created yet,
+        # no files to clearout. (this is not an error)
+        return
+
+    for file in glob.glob(output_folder + "/*." + extension):
+        # print("removed pre-existing file: " + file)
+        os.remove(file)
+
+
+def folder_not_found(folder):
+    abs_folder = base_folder_path + folder + "/"
+    return not os.path.isdir(abs_folder)
+
+
+def find_files_in_folder(folder, sub_folder, include_list, extension, sought_exceptions, found_exceptions):
+    abs_folder = base_folder_path + folder + "/" + sub_folder
+
+    if not os.path.isdir(abs_folder):
+        print("ERROR " + abs_folder + " not found.")
+        return include_list, found_exceptions
+
+    os.chdir(abs_folder)
+
+    sub_folder_slashed = ""
+    if sub_folder != "":
+        sub_folder_slashed = sub_folder + "/"
+
+    for file in glob.glob("*." + extension):
+
+        simple_name = Path(file).stem
+
+        if file.endswith(".gen.cpp"):
+            continue
+
+        li = '#include "../' + sub_folder_slashed + file + '"'
+
+        if not simple_name in sought_exceptions:
+            include_list.append(li)
+        else:
+            found_exceptions.append(li)
+
+    return include_list, found_exceptions
+
+
+def write_output_file(file_count, include_list, start_line, end_line, output_folder, output_filename_prefix, extension):
+
+    output_folder = os.path.abspath(output_folder)
+
+    if not os.path.isdir(output_folder):
+        # create
+        os.mkdir(output_folder)
+        if not os.path.isdir(output_folder):
+            print("ERROR " + output_folder + " could not be created.")
+            return
+        print("CREATING folder " + output_folder)
+
+    file_text = ""
+
+    for l in range(start_line, end_line):
+        if l < len(include_list):
+            line = include_list[l]
+            li = line + "\n"
+            file_text += li
+
+    # print(file_text)
+
+    num_string = ""
+    if file_count > 0:
+        num_string = "_" + str(file_count)
+
+    short_filename = output_filename_prefix + num_string + ".gen." + extension
+    output_filename = output_folder + "/" + short_filename
+    if _verbose:
+        print("generating: " + short_filename)
+
+    output_path = Path(output_filename)
+    output_path.write_text(file_text, encoding="utf8")
+
+
+def write_exception_output_file(file_count, exception_string, output_folder, output_filename_prefix, extension):
+    output_folder = os.path.abspath(output_folder)
+    if not os.path.isdir(output_folder):
+        print("ERROR " + output_folder + " does not exist.")
+        return
+
+    file_text = exception_string + "\n"
+
+    num_string = ""
+    if file_count > 0:
+        num_string = "_" + str(file_count)
+
+    short_filename = output_filename_prefix + "_exception" + num_string + ".gen." + extension
+    output_filename = output_folder + "/" + short_filename
+
+    if _verbose:
+        print("generating: " + short_filename)
+
+    # print("text: " + file_text)
+    # return
+    output_path = Path(output_filename)
+    output_path.write_text(file_text, encoding="utf8")
+
+
+def find_section_name(sub_folder):
+    # Construct a useful name for the section from the path for debug logging
+    section_path = os.path.abspath(base_folder_path + sub_folder) + "/"
+
+    folders = []
+    folder = ""
+
+    for i in range(8):
+        folder = os.path.dirname(section_path)
+        folder = os.path.basename(folder)
+        if folder == base_folder_only:
+            break
+        folders.append(folder)
+        section_path += "../"
+        section_path = os.path.abspath(section_path) + "/"
+
+    section_name = ""
+    for n in range(len(folders)):
+        section_name += folders[len(folders) - n - 1]
+        if n != (len(folders) - 1):
+            section_name += "_"
+
+    return section_name
+
+
+# "folders" is a list of folders to add all the files from to add to the SCU
+# "section (like a module)". The name of the scu file will be derived from the first folder
+# (thus e.g. scene/3d becomes scu_scene_3d.gen.cpp)
+
+# "includes_per_scu" limits the number of includes in a single scu file.
+# This allows the module to be built in several translation units instead of just 1.
+# This will usually be slower to compile but will use less memory per compiler instance, which
+# is most relevant in release builds.
+
+# "sought_exceptions" are a list of files (without extension) that contain
+# e.g. naming conflicts, and are therefore not suitable for the scu build.
+# These will automatically be placed in their own separate scu file,
+# which is slow like a normal build, but prevents the naming conflicts.
+# Ideally in these situations, the source code should be changed to prevent naming conflicts.
+
+# "extension" will usually be cpp, but can also be set to c (for e.g. third party libraries that use c)
+def process_folder(folders, sought_exceptions=[], includes_per_scu=0, extension="cpp"):
+    if len(folders) == 0:
+        return
+
+    # Construct the filename prefix from the FIRST folder name
+    # e.g. "scene_3d"
+    out_filename = find_section_name(folders[0])
+
+    found_includes = []
+    found_exceptions = []
+
+    main_folder = folders[0]
+    abs_main_folder = base_folder_path + main_folder
+
+    # Keep a record of all folders that have been processed for SCU,
+    # this enables deciding what to do when we call "add_source_files()"
+    global _scu_folders
+    _scu_folders.add(main_folder)
+
+    # main folder (first)
+    found_includes, found_exceptions = find_files_in_folder(
+        main_folder, "", found_includes, extension, sought_exceptions, found_exceptions
+    )
+
+    # sub folders
+    for d in range(1, len(folders)):
+        found_includes, found_exceptions = find_files_in_folder(
+            main_folder, folders[d], found_includes, extension, sought_exceptions, found_exceptions
+        )
+
+    found_includes = sorted(found_includes)
+
+    # calculate how many lines to write in each file
+    total_lines = len(found_includes)
+
+    # adjust number of output files according to whether DEV or release
+    num_output_files = 1
+    if _is_release_build:
+        # always have a maximum in release
+        includes_per_scu = 8
+        num_output_files = max(math.ceil(total_lines / float(includes_per_scu)), 1)
+    else:
+        if includes_per_scu > 0:
+            num_output_files = max(math.ceil(total_lines / float(includes_per_scu)), 1)
+
+    lines_per_file = math.ceil(total_lines / float(num_output_files))
+    lines_per_file = max(lines_per_file, 1)
+
+    start_line = 0
+    file_number = 0
+
+    # These do not vary throughout the loop
+    output_folder = abs_main_folder + "/scu/"
+    output_filename_prefix = "scu_" + out_filename
+
+    # Clear out any existing files (usually we will be overwriting,
+    # but we want to remove any that are pre-existing that will not be
+    # overwritten, so as to not compile anything stale)
+    clear_out_existing_files(output_folder, extension)
+
+    for file_count in range(0, num_output_files):
+        end_line = start_line + lines_per_file
+
+        # special case to cover rounding error in final file
+        if file_count == (num_output_files - 1):
+            end_line = len(found_includes)
+
+        write_output_file(
+            file_count, found_includes, start_line, end_line, output_folder, output_filename_prefix, extension
+        )
+
+        start_line = end_line
+
+    # Write the exceptions each in their own scu gen file,
+    # so they can effectively compile in "old style / normal build".
+    for exception_count in range(len(found_exceptions)):
+        write_exception_output_file(
+            exception_count, found_exceptions[exception_count], output_folder, output_filename_prefix, extension
+        )
+
+
+def generate_scu_files(verbose, is_release_build):
+
+    print("=============================")
+    print("Single Compilation Unit Build")
+    print("=============================")
+    print("Generating SCU build files")
+    global _verbose
+    _verbose = verbose
+    global _is_release_build
+    _is_release_build = is_release_build
+
+    curr_folder = os.path.abspath("./")
+
+    # check we are running from the correct folder
+    if folder_not_found("core") or folder_not_found("platform") or folder_not_found("scene"):
+        raise RuntimeError("scu_builders.py must be run from the godot folder.")
+        return
+
+    process_folder(["core"])
+    process_folder(["core/crypto"])
+    process_folder(["core/debugger"])
+    process_folder(["core/extension"])
+    process_folder(["core/input"])
+    process_folder(["core/io"])
+    process_folder(["core/math"])
+    process_folder(["core/object"])
+    process_folder(["core/os"])
+    process_folder(["core/string"])
+    process_folder(["core/variant"], ["variant_utility"])
+
+    process_folder(["drivers/unix"])
+    process_folder(["drivers/png"])
+
+    process_folder(["editor"], ["file_system_dock", "editor_resource_preview"], 32)
+    process_folder(["editor/debugger"])
+    process_folder(["editor/debugger/debug_adapter"])
+    process_folder(["editor/export"])
+    process_folder(["editor/gui"])
+    process_folder(["editor/import"])
+    process_folder(["editor/plugins"])
+    process_folder(["editor/plugins/gizmos"])
+    process_folder(["editor/plugins/tiles"])
+
+    process_folder(["platform/android/export"])
+    process_folder(["platform/ios/export"])
+    process_folder(["platform/linuxbsd/export"])
+    process_folder(["platform/macos/export"])
+    process_folder(["platform/uwp/export"])
+    process_folder(["platform/web/export"])
+    process_folder(["platform/windows/export"])
+
+    process_folder(["modules/gltf"])
+    process_folder(["modules/gltf/structures"])
+    process_folder(["modules/gltf/editor"])
+    process_folder(["modules/gltf/extensions"])
+    process_folder(["modules/gltf/extensions/physics"])
+    process_folder(["modules/navigation"])
+    process_folder(["modules/webrtc"])
+    process_folder(["modules/websocket"])
+    process_folder(["modules/gridmap"])
+    process_folder(["modules/multiplayer"])
+    process_folder(["modules/multiplayer/editor"])
+    process_folder(["modules/openxr"], ["register_types"])
+    process_folder(["modules/openxr/action_map"])
+    process_folder(["modules/openxr/editor"])
+
+    process_folder(["modules/csg"])
+    process_folder(["modules/gdscript"])
+    process_folder(["modules/gdscript/editor"])
+    process_folder(["modules/gdscript/language_server"])
+
+    process_folder(["scene/2d"])
+    process_folder(["scene/3d"])
+    process_folder(["scene/animation"])
+    process_folder(["scene/gui"])
+    process_folder(["scene/main"])
+    process_folder(["scene/resources"])
+
+    process_folder(["servers"])
+    process_folder(["servers/rendering"])
+    process_folder(["servers/rendering/storage"])
+    process_folder(["servers/rendering/renderer_rd"])
+    process_folder(["servers/rendering/renderer_rd/effects"])
+    process_folder(["servers/rendering/renderer_rd/environment"])
+    process_folder(["servers/rendering/renderer_rd/storage_rd"])
+    process_folder(["servers/physics_2d"])
+    process_folder(["servers/physics_3d"])
+    process_folder(["servers/physics_3d/joints"])
+    process_folder(["servers/audio"])
+    process_folder(["servers/audio/effects"])
+
+    # Finally change back the path to the calling folder
+    os.chdir(curr_folder)
+
+    return _scu_folders

+ 33 - 0
servers/physics_3d/godot_joint_3d.h

@@ -39,6 +39,39 @@ protected:
 	bool dynamic_A = false;
 	bool dynamic_B = false;
 
+	void plane_space(const Vector3 &n, Vector3 &p, Vector3 &q) {
+		if (Math::abs(n.z) > Math_SQRT12) {
+			// choose p in y-z plane
+			real_t a = n[1] * n[1] + n[2] * n[2];
+			real_t k = 1.0 / Math::sqrt(a);
+			p = Vector3(0, -n[2] * k, n[1] * k);
+			// set q = n x p
+			q = Vector3(a * k, -n[0] * p[2], n[0] * p[1]);
+		} else {
+			// choose p in x-y plane
+			real_t a = n.x * n.x + n.y * n.y;
+			real_t k = 1.0 / Math::sqrt(a);
+			p = Vector3(-n.y * k, n.x * k, 0);
+			// set q = n x p
+			q = Vector3(-n.z * p.y, n.z * p.x, a * k);
+		}
+	}
+
+	_FORCE_INLINE_ real_t atan2fast(real_t y, real_t x) {
+		real_t coeff_1 = Math_PI / 4.0f;
+		real_t coeff_2 = 3.0f * coeff_1;
+		real_t abs_y = Math::abs(y);
+		real_t angle;
+		if (x >= 0.0f) {
+			real_t r = (x - abs_y) / (x + abs_y);
+			angle = coeff_1 - coeff_1 * r;
+		} else {
+			real_t r = (x + abs_y) / (abs_y - x);
+			angle = coeff_2 - coeff_1 * r;
+		}
+		return (y < 0.0f) ? -angle : angle;
+	}
+
 public:
 	virtual bool setup(real_t p_step) override { return false; }
 	virtual bool pre_solve(real_t p_step) override { return true; }

+ 0 - 33
servers/physics_3d/joints/godot_cone_twist_joint_3d.cpp

@@ -51,39 +51,6 @@ Written by: Marcus Hennix
 
 #include "godot_cone_twist_joint_3d.h"
 
-static void plane_space(const Vector3 &n, Vector3 &p, Vector3 &q) {
-	if (Math::abs(n.z) > Math_SQRT12) {
-		// choose p in y-z plane
-		real_t a = n[1] * n[1] + n[2] * n[2];
-		real_t k = 1.0 / Math::sqrt(a);
-		p = Vector3(0, -n[2] * k, n[1] * k);
-		// set q = n x p
-		q = Vector3(a * k, -n[0] * p[2], n[0] * p[1]);
-	} else {
-		// choose p in x-y plane
-		real_t a = n.x * n.x + n.y * n.y;
-		real_t k = 1.0 / Math::sqrt(a);
-		p = Vector3(-n.y * k, n.x * k, 0);
-		// set q = n x p
-		q = Vector3(-n.z * p.y, n.z * p.x, a * k);
-	}
-}
-
-static _FORCE_INLINE_ real_t atan2fast(real_t y, real_t x) {
-	real_t coeff_1 = Math_PI / 4.0f;
-	real_t coeff_2 = 3.0f * coeff_1;
-	real_t abs_y = Math::abs(y);
-	real_t angle;
-	if (x >= 0.0f) {
-		real_t r = (x - abs_y) / (x + abs_y);
-		angle = coeff_1 - coeff_1 * r;
-	} else {
-		real_t r = (x + abs_y) / (abs_y - x);
-		angle = coeff_2 - coeff_1 * r;
-	}
-	return (y < 0.0f) ? -angle : angle;
-}
-
 GodotConeTwistJoint3D::GodotConeTwistJoint3D(GodotBody3D *rbA, GodotBody3D *rbB, const Transform3D &rbAFrame, const Transform3D &rbBFrame) :
 		GodotJoint3D(_arr, 2) {
 	A = rbA;

+ 0 - 33
servers/physics_3d/joints/godot_hinge_joint_3d.cpp

@@ -49,24 +49,6 @@ subject to the following restrictions:
 
 #include "godot_hinge_joint_3d.h"
 
-static void plane_space(const Vector3 &n, Vector3 &p, Vector3 &q) {
-	if (Math::abs(n.z) > Math_SQRT12) {
-		// choose p in y-z plane
-		real_t a = n[1] * n[1] + n[2] * n[2];
-		real_t k = 1.0 / Math::sqrt(a);
-		p = Vector3(0, -n[2] * k, n[1] * k);
-		// set q = n x p
-		q = Vector3(a * k, -n[0] * p[2], n[0] * p[1]);
-	} else {
-		// choose p in x-y plane
-		real_t a = n.x * n.x + n.y * n.y;
-		real_t k = 1.0 / Math::sqrt(a);
-		p = Vector3(-n.y * k, n.x * k, 0);
-		// set q = n x p
-		q = Vector3(-n.z * p.y, n.z * p.x, a * k);
-	}
-}
-
 GodotHingeJoint3D::GodotHingeJoint3D(GodotBody3D *rbA, GodotBody3D *rbB, const Transform3D &frameA, const Transform3D &frameB) :
 		GodotJoint3D(_arr, 2) {
 	A = rbA;
@@ -368,21 +350,6 @@ void	HingeJointSW::updateRHS(real_t	timeStep)
 
 */
 
-static _FORCE_INLINE_ real_t atan2fast(real_t y, real_t x) {
-	real_t coeff_1 = Math_PI / 4.0f;
-	real_t coeff_2 = 3.0f * coeff_1;
-	real_t abs_y = Math::abs(y);
-	real_t angle;
-	if (x >= 0.0f) {
-		real_t r = (x - abs_y) / (x + abs_y);
-		angle = coeff_1 - coeff_1 * r;
-	} else {
-		real_t r = (x + abs_y) / (abs_y - x);
-		angle = coeff_2 - coeff_1 * r;
-	}
-	return (y < 0.0f) ? -angle : angle;
-}
-
 real_t GodotHingeJoint3D::get_hinge_angle() {
 	const Vector3 refAxis0 = A->get_transform().basis.xform(m_rbAFrame.basis.get_column(0));
 	const Vector3 refAxis1 = A->get_transform().basis.xform(m_rbAFrame.basis.get_column(1));

+ 0 - 19
servers/physics_3d/joints/godot_slider_joint_3d.cpp

@@ -57,25 +57,6 @@ April 04, 2008
 
 //-----------------------------------------------------------------------------
 
-static _FORCE_INLINE_ real_t atan2fast(real_t y, real_t x) {
-	real_t coeff_1 = Math_PI / 4.0f;
-	real_t coeff_2 = 3.0f * coeff_1;
-	real_t abs_y = Math::abs(y);
-	real_t angle;
-	if (x >= 0.0f) {
-		real_t r = (x - abs_y) / (x + abs_y);
-		angle = coeff_1 - coeff_1 * r;
-	} else {
-		real_t r = (x + abs_y) / (abs_y - x);
-		angle = coeff_2 - coeff_1 * r;
-	}
-	return (y < 0.0f) ? -angle : angle;
-}
-
-//-----------------------------------------------------------------------------
-
-//-----------------------------------------------------------------------------
-
 GodotSliderJoint3D::GodotSliderJoint3D(GodotBody3D *rbA, GodotBody3D *rbB, const Transform3D &frameInA, const Transform3D &frameInB) :
 		GodotJoint3D(_arr, 2),
 		m_frameInA(frameInA),

+ 0 - 2
servers/rendering/renderer_canvas_cull.cpp

@@ -36,8 +36,6 @@
 #include "rendering_server_globals.h"
 #include "servers/rendering/storage/texture_storage.h"
 
-static const int z_range = RS::CANVAS_ITEM_Z_MAX - RS::CANVAS_ITEM_Z_MIN + 1;
-
 void RendererCanvasCull::_render_canvas_item_tree(RID p_to_render_target, Canvas::ChildItem *p_child_items, int p_child_item_count, Item *p_canvas_item, const Transform2D &p_transform, const Rect2 &p_clip_rect, const Color &p_modulate, RendererCanvasRender::Light *p_lights, RendererCanvasRender::Light *p_directional_lights, RenderingServer::CanvasItemTextureFilter p_default_filter, RenderingServer::CanvasItemTextureRepeat p_default_repeat, bool p_snap_2d_vertices_to_pixel, uint32_t canvas_cull_mask) {
 	RENDER_TIMESTAMP("Cull CanvasItem Tree");
 

+ 2 - 0
servers/rendering/renderer_canvas_cull.h

@@ -183,6 +183,8 @@ private:
 	void _render_canvas_item_tree(RID p_to_render_target, Canvas::ChildItem *p_child_items, int p_child_item_count, Item *p_canvas_item, const Transform2D &p_transform, const Rect2 &p_clip_rect, const Color &p_modulate, RendererCanvasRender::Light *p_lights, RendererCanvasRender::Light *p_directional_lights, RS::CanvasItemTextureFilter p_default_filter, RS::CanvasItemTextureRepeat p_default_repeat, bool p_snap_2d_vertices_to_pixel, uint32_t canvas_cull_mask);
 	void _cull_canvas_item(Item *p_canvas_item, const Transform2D &p_transform, const Rect2 &p_clip_rect, const Color &p_modulate, int p_z, RendererCanvasRender::Item **r_z_list, RendererCanvasRender::Item **r_z_last_list, Item *p_canvas_clip, Item *p_material_owner, bool allow_y_sort, uint32_t canvas_cull_mask);
 
+	static constexpr int z_range = RS::CANVAS_ITEM_Z_MAX - RS::CANVAS_ITEM_Z_MIN + 1;
+
 	RendererCanvasRender::Item **z_list;
 	RendererCanvasRender::Item **z_last_list;