summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--COPYRIGHT.txt6
-rw-r--r--core/SCsub1
-rw-r--r--core/config/engine.cpp8
-rw-r--r--core/config/engine.h5
-rw-r--r--core/math/bvh_logic.inc14
-rw-r--r--core/math/bvh_public.inc46
-rw-r--r--core/math/bvh_refit.inc4
-rw-r--r--core/math/bvh_structs.inc1
-rw-r--r--core/math/bvh_tree.h15
-rw-r--r--doc/classes/EditorInterface.xml6
-rw-r--r--doc/classes/EditorPaths.xml49
-rw-r--r--doc/classes/EditorSettings.xml9
-rw-r--r--doc/classes/ProjectSettings.xml10
-rw-r--r--doc/classes/VisualShaderNodeBillboard.xml38
-rw-r--r--drivers/unix/dir_access_unix.cpp2
-rw-r--r--editor/code_editor.cpp2
-rw-r--r--editor/editor_audio_buses.cpp41
-rw-r--r--editor/editor_audio_buses.h8
-rw-r--r--editor/editor_export.cpp10
-rw-r--r--editor/editor_feature_profile.cpp36
-rw-r--r--editor/editor_feature_profile.h6
-rw-r--r--editor/editor_node.cpp29
-rw-r--r--editor/editor_node.h3
-rw-r--r--editor/editor_paths.cpp156
-rw-r--r--editor/editor_paths.h72
-rw-r--r--editor/editor_plugin.cpp5
-rw-r--r--editor/editor_plugin.h2
-rw-r--r--editor/editor_resource_preview.cpp2
-rw-r--r--editor/editor_settings.cpp106
-rw-r--r--editor/editor_settings.h8
-rw-r--r--editor/editor_themes.cpp13
-rw-r--r--editor/export_template_manager.cpp2
-rw-r--r--editor/plugins/asset_library_editor_plugin.cpp8
-rw-r--r--editor/plugins/canvas_item_editor_plugin.cpp2
-rw-r--r--editor/plugins/editor_preview_plugins.cpp2
-rw-r--r--editor/plugins/script_text_editor.cpp10
-rw-r--r--editor/plugins/visual_shader_editor_plugin.cpp1
-rw-r--r--main/main.cpp6
-rw-r--r--misc/dist/ios_xcode/godot_ios.xcodeproj/project.pbxproj8
-rw-r--r--modules/glslang/register_types.cpp7
-rw-r--r--modules/gltf/doc_classes/GLTFNode.xml2
-rw-r--r--modules/gltf/gltf_document.cpp236
-rw-r--r--modules/gltf/gltf_document.h6
-rw-r--r--modules/gltf/gltf_node.cpp11
-rw-r--r--modules/gltf/gltf_node.h4
-rw-r--r--modules/mono/godotsharp_dirs.cpp4
-rw-r--r--platform/android/export/export.cpp4
-rw-r--r--platform/iphone/export/export.cpp16
-rw-r--r--platform/javascript/export/export.cpp6
-rw-r--r--platform/osx/export/export.cpp8
-rw-r--r--platform/uwp/export/export.cpp6
-rw-r--r--scene/2d/camera_2d.cpp9
-rw-r--r--scene/3d/skeleton_3d.cpp2
-rw-r--r--scene/register_scene_types.cpp1
-rw-r--r--scene/resources/visual_shader.cpp27
-rw-r--r--scene/resources/visual_shader.h5
-rw-r--r--scene/resources/visual_shader_nodes.cpp124
-rw-r--r--scene/resources/visual_shader_nodes.h47
-rw-r--r--servers/rendering/renderer_canvas_render.h1
-rw-r--r--servers/rendering/renderer_rd/renderer_compositor_rd.cpp42
-rw-r--r--servers/rendering/renderer_rd/renderer_compositor_rd.h2
-rw-r--r--servers/rendering/renderer_rd/shader_compiler_rd.cpp101
-rw-r--r--servers/rendering/renderer_rd/shader_rd.cpp290
-rw-r--r--servers/rendering/renderer_rd/shader_rd.h23
-rw-r--r--servers/rendering/rendering_device.cpp12
-rw-r--r--servers/rendering/rendering_device.h4
-rw-r--r--servers/rendering_server.cpp6
-rw-r--r--thirdparty/README.md5
-rw-r--r--thirdparty/misc/smolv.cpp2108
-rw-r--r--thirdparty/misc/smolv.h169
70 files changed, 3637 insertions, 403 deletions
diff --git a/COPYRIGHT.txt b/COPYRIGHT.txt
index c007a7e2b3..b2a930a4bb 100644
--- a/COPYRIGHT.txt
+++ b/COPYRIGHT.txt
@@ -360,6 +360,12 @@ Comment: SMAZ
Copyright: 2006-2009, Salvatore Sanfilippo
License: BSD-3-clause
+Files: ./thirdparty/misc/smolv.cpp
+ ./thirdparty/misc/smolv.h
+Comment: SMOL-V
+Copyright: 2016-2020, Aras Pranckevicius
+License: public-domain or Unlicense or Expat
+
Files: ./thirdparty/misc/stb_rect_pack.h
./thirdparty/misc/stb_vorbis.c
Comment: stb libraries
diff --git a/core/SCsub b/core/SCsub
index bdf8544840..e3ba46be02 100644
--- a/core/SCsub
+++ b/core/SCsub
@@ -59,6 +59,7 @@ thirdparty_misc_sources = [
"pcg.cpp",
"polypartition.cpp",
"clipper.cpp",
+ "smolv.cpp",
]
thirdparty_misc_sources = [thirdparty_misc_dir + file for file in thirdparty_misc_sources]
env_thirdparty.add_source_files(thirdparty_obj, thirdparty_misc_sources)
diff --git a/core/config/engine.cpp b/core/config/engine.cpp
index 2360d66438..c43e32868c 100644
--- a/core/config/engine.cpp
+++ b/core/config/engine.cpp
@@ -31,6 +31,7 @@
#include "engine.h"
#include "core/authors.gen.h"
+#include "core/config/project_settings.h"
#include "core/donors.gen.h"
#include "core/license.gen.h"
#include "core/version.h"
@@ -210,6 +211,13 @@ void Engine::get_singletons(List<Singleton> *p_singletons) {
}
}
+void Engine::set_shader_cache_path(const String &p_path) {
+ shader_cache_path = p_path;
+}
+String Engine::get_shader_cache_path() const {
+ return shader_cache_path;
+}
+
Engine *Engine::singleton = nullptr;
Engine *Engine::get_singleton() {
diff --git a/core/config/engine.h b/core/config/engine.h
index a9080e3dfd..276da1c7ea 100644
--- a/core/config/engine.h
+++ b/core/config/engine.h
@@ -72,6 +72,8 @@ private:
static Engine *singleton;
+ String shader_cache_path;
+
public:
static Engine *get_singleton();
@@ -121,6 +123,9 @@ public:
Dictionary get_license_info() const;
String get_license_text() const;
+ void set_shader_cache_path(const String &p_path);
+ String get_shader_cache_path() const;
+
bool is_abort_on_gpu_errors_enabled() const;
bool is_validation_layers_enabled() const;
diff --git a/core/math/bvh_logic.inc b/core/math/bvh_logic.inc
index d84c3f7830..afab08f151 100644
--- a/core/math/bvh_logic.inc
+++ b/core/math/bvh_logic.inc
@@ -20,17 +20,17 @@ void _logic_item_remove_and_reinsert(uint32_t p_ref_id) {
// some overlay elaborate way to find out which tree the node is in!
BVHHandle temp_handle;
temp_handle.set_id(p_ref_id);
- _current_tree = _handle_get_tree_id(temp_handle);
+ uint32_t tree_id = _handle_get_tree_id(temp_handle);
// remove and reinsert
BVHABB_CLASS abb;
- node_remove_item(p_ref_id, &abb);
+ node_remove_item(p_ref_id, tree_id, &abb);
// we must choose where to add to tree
- ref.tnode_id = _logic_choose_item_add_node(_root_node_id[_current_tree], abb);
+ ref.tnode_id = _logic_choose_item_add_node(_root_node_id[tree_id], abb);
_node_add_item(ref.tnode_id, p_ref_id, abb);
- refit_upward_and_balance(ref.tnode_id);
+ refit_upward_and_balance(ref.tnode_id, tree_id);
}
// from randy gaul balance function
@@ -66,7 +66,7 @@ BVHABB_CLASS _logic_abb_merge(const BVHABB_CLASS &a, const BVHABB_CLASS &b) {
// https://github.com/RandyGaul/qu3e
// It is MODIFIED from qu3e version.
// This is the only function used (and _logic_abb_merge helper function).
-int32_t _logic_balance(int32_t iA) {
+int32_t _logic_balance(int32_t iA, uint32_t p_tree_id) {
// return iA; // uncomment this to bypass balance
TNode *A = &_nodes[iA];
@@ -107,7 +107,7 @@ int32_t _logic_balance(int32_t iA) {
}
} else {
// check this .. seems dodgy
- change_root_node(iC);
+ change_root_node(iC, p_tree_id);
}
// Swap A and C
@@ -159,7 +159,7 @@ int32_t _logic_balance(int32_t iA) {
else {
// check this .. seems dodgy
- change_root_node(iB);
+ change_root_node(iB, p_tree_id);
}
// Swap A and B
diff --git a/core/math/bvh_public.inc b/core/math/bvh_public.inc
index f1b6d6b1bf..2c1e406712 100644
--- a/core/math/bvh_public.inc
+++ b/core/math/bvh_public.inc
@@ -53,16 +53,16 @@ BVHHandle item_add(T *p_userdata, bool p_active, const Bounds &p_aabb, int32_t p
// assign to handle to return
handle.set_id(ref_id);
- _current_tree = 0;
+ uint32_t tree_id = 0;
if (p_pairable) {
- _current_tree = 1;
+ tree_id = 1;
}
- create_root_node(_current_tree);
+ create_root_node(tree_id);
// we must choose where to add to tree
if (p_active) {
- ref->tnode_id = _logic_choose_item_add_node(_root_node_id[_current_tree], abb);
+ ref->tnode_id = _logic_choose_item_add_node(_root_node_id[tree_id], abb);
bool refit = _node_add_item(ref->tnode_id, ref_id, abb);
@@ -70,7 +70,7 @@ BVHHandle item_add(T *p_userdata, bool p_active, const Bounds &p_aabb, int32_t p
// only need to refit from the parent
const TNode &add_node = _nodes[ref->tnode_id];
if (add_node.parent_id != BVHCommon::INVALID) {
- refit_upward_and_balance(add_node.parent_id);
+ refit_upward_and_balance(add_node.parent_id, tree_id);
}
}
} else {
@@ -139,13 +139,13 @@ bool item_move(BVHHandle p_handle, const Bounds &p_aabb) {
return true;
}
- _current_tree = _handle_get_tree_id(p_handle);
+ uint32_t tree_id = _handle_get_tree_id(p_handle);
// remove and reinsert
- node_remove_item(ref_id);
+ node_remove_item(ref_id, tree_id);
// we must choose where to add to tree
- ref.tnode_id = _logic_choose_item_add_node(_root_node_id[_current_tree], abb);
+ ref.tnode_id = _logic_choose_item_add_node(_root_node_id[tree_id], abb);
// add to the tree
bool needs_refit = _node_add_item(ref.tnode_id, ref_id, abb);
@@ -167,7 +167,7 @@ bool item_move(BVHHandle p_handle, const Bounds &p_aabb) {
void item_remove(BVHHandle p_handle) {
uint32_t ref_id = p_handle.id();
- _current_tree = _handle_get_tree_id(p_handle);
+ uint32_t tree_id = _handle_get_tree_id(p_handle);
VERBOSE_PRINT("item_remove [" + itos(ref_id) + "] ");
@@ -187,7 +187,7 @@ void item_remove(BVHHandle p_handle) {
// remove the item from the node (only if active)
if (_refs[ref_id].is_active()) {
- node_remove_item(ref_id);
+ node_remove_item(ref_id, tree_id);
}
// remove the item reference
@@ -198,10 +198,10 @@ void item_remove(BVHHandle p_handle) {
}
// don't think refit_all is necessary?
- //refit_all(_current_tree);
+ //refit_all(_tree_id);
#ifdef BVH_VERBOSE_TREE
- _debug_recursive_print_tree(_current_tree);
+ _debug_recursive_print_tree(tree_id);
#endif
}
@@ -218,13 +218,13 @@ bool item_activate(BVHHandle p_handle, const Bounds &p_aabb) {
BVHABB_CLASS abb;
abb.from(p_aabb);
- _current_tree = _handle_get_tree_id(p_handle);
+ uint32_t tree_id = _handle_get_tree_id(p_handle);
// we must choose where to add to tree
- ref.tnode_id = _logic_choose_item_add_node(_root_node_id[_current_tree], abb);
+ ref.tnode_id = _logic_choose_item_add_node(_root_node_id[tree_id], abb);
_node_add_item(ref.tnode_id, ref_id, abb);
- refit_upward_and_balance(ref.tnode_id);
+ refit_upward_and_balance(ref.tnode_id, tree_id);
return true;
}
@@ -238,9 +238,11 @@ bool item_deactivate(BVHHandle p_handle) {
return false;
}
+ uint32_t tree_id = _handle_get_tree_id(p_handle);
+
// remove from tree
BVHABB_CLASS abb;
- node_remove_item(ref_id, &abb);
+ node_remove_item(ref_id, tree_id, &abb);
// mark as inactive
ref.set_inactive();
@@ -304,21 +306,21 @@ bool item_set_pairable(const BVHHandle &p_handle, bool p_pairable, uint32_t p_pa
BVHABB_CLASS abb = leaf.get_aabb(ref.item_id);
// make sure current tree is correct prior to changing
- _current_tree = _handle_get_tree_id(p_handle);
+ uint32_t tree_id = _handle_get_tree_id(p_handle);
// remove from old tree
- node_remove_item(ref_id);
+ node_remove_item(ref_id, tree_id);
// we must set the pairable AFTER getting the current tree
// because the pairable status determines which tree
ex.pairable = p_pairable;
// add to new tree
- _current_tree = _handle_get_tree_id(p_handle);
- create_root_node(_current_tree);
+ tree_id = _handle_get_tree_id(p_handle);
+ create_root_node(tree_id);
// we must choose where to add to tree
- ref.tnode_id = _logic_choose_item_add_node(_root_node_id[_current_tree], abb);
+ ref.tnode_id = _logic_choose_item_add_node(_root_node_id[tree_id], abb);
bool needs_refit = _node_add_item(ref.tnode_id, ref_id, abb);
// only need to refit from the PARENT
@@ -326,7 +328,7 @@ bool item_set_pairable(const BVHHandle &p_handle, bool p_pairable, uint32_t p_pa
// only need to refit from the parent
const TNode &add_node = _nodes[ref.tnode_id];
if (add_node.parent_id != BVHCommon::INVALID) {
- refit_upward_and_balance(add_node.parent_id);
+ refit_upward_and_balance(add_node.parent_id, tree_id);
}
}
} else {
diff --git a/core/math/bvh_refit.inc b/core/math/bvh_refit.inc
index 514c853ac5..717a3438c7 100644
--- a/core/math/bvh_refit.inc
+++ b/core/math/bvh_refit.inc
@@ -66,10 +66,10 @@ void refit_upward(uint32_t p_node_id) {
}
}
-void refit_upward_and_balance(uint32_t p_node_id) {
+void refit_upward_and_balance(uint32_t p_node_id, uint32_t p_tree_id) {
while (p_node_id != BVHCommon::INVALID) {
uint32_t before = p_node_id;
- p_node_id = _logic_balance(p_node_id);
+ p_node_id = _logic_balance(p_node_id, p_tree_id);
if (before != p_node_id) {
VERBOSE_PRINT("REBALANCED!");
diff --git a/core/math/bvh_structs.inc b/core/math/bvh_structs.inc
index 4133ba6c10..1d1e0e6468 100644
--- a/core/math/bvh_structs.inc
+++ b/core/math/bvh_structs.inc
@@ -162,7 +162,6 @@ enum { NUM_TREES = 2,
// Tree 1 - Pairable
// This is more efficient because in physics we only need check non pairable against the pairable tree.
uint32_t _root_node_id[NUM_TREES];
-int _current_tree = 0;
// these values may need tweaking according to the project
// the bound of the world, and the average velocities of the objects
diff --git a/core/math/bvh_tree.h b/core/math/bvh_tree.h
index 64c5f6e254..3169d31ec7 100644
--- a/core/math/bvh_tree.h
+++ b/core/math/bvh_tree.h
@@ -196,7 +196,7 @@ private:
new_child.parent_id = p_parent_id;
}
- void node_remove_child(uint32_t p_parent_id, uint32_t p_child_id, bool p_prevent_sibling = false) {
+ void node_remove_child(uint32_t p_parent_id, uint32_t p_child_id, uint32_t p_tree_id, bool p_prevent_sibling = false) {
TNode &parent = _nodes[p_parent_id];
BVH_ASSERT(!parent.is_leaf());
@@ -231,7 +231,7 @@ private:
if (grandparent_id == BVHCommon::INVALID) {
if (sibling_present) {
// change the root node
- change_root_node(sibling_id);
+ change_root_node(sibling_id, p_tree_id);
// delete the old root node as no longer needed
_nodes.free(p_parent_id);
@@ -243,16 +243,15 @@ private:
if (sibling_present) {
node_replace_child(grandparent_id, p_parent_id, sibling_id);
} else {
- node_remove_child(grandparent_id, p_parent_id, true);
+ node_remove_child(grandparent_id, p_parent_id, p_tree_id, true);
}
// put the node on the free list to recycle
_nodes.free(p_parent_id);
}
- // this relies on _current_tree being accurate
- void change_root_node(uint32_t p_new_root_id) {
- _root_node_id[_current_tree] = p_new_root_id;
+ void change_root_node(uint32_t p_new_root_id, uint32_t p_tree_id) {
+ _root_node_id[p_tree_id] = p_new_root_id;
TNode &root = _nodes[p_new_root_id];
// mark no parent
@@ -272,7 +271,7 @@ private:
node.neg_leaf_id = -(int)child_leaf_id;
}
- void node_remove_item(uint32_t p_ref_id, BVHABB_CLASS *r_old_aabb = nullptr) {
+ void node_remove_item(uint32_t p_ref_id, uint32_t p_tree_id, BVHABB_CLASS *r_old_aabb = nullptr) {
// get the reference
ItemRef &ref = _refs[p_ref_id];
uint32_t owner_node_id = ref.tnode_id;
@@ -336,7 +335,7 @@ private:
uint32_t parent_id = tnode.parent_id;
- node_remove_child(parent_id, owner_node_id);
+ node_remove_child(parent_id, owner_node_id, p_tree_id);
refit_upward(parent_id);
// put the node on the free list to recycle
diff --git a/doc/classes/EditorInterface.xml b/doc/classes/EditorInterface.xml
index a5328ce382..3f324bf1a0 100644
--- a/doc/classes/EditorInterface.xml
+++ b/doc/classes/EditorInterface.xml
@@ -57,6 +57,12 @@
[b]Note:[/b] This returns the main editor control containing the whole editor, not the 2D or 3D viewports specifically.
</description>
</method>
+ <method name="get_editor_paths">
+ <return type="EditorPaths">
+ </return>
+ <description>
+ </description>
+ </method>
<method name="get_editor_scale" qualifiers="const">
<return type="float">
</return>
diff --git a/doc/classes/EditorPaths.xml b/doc/classes/EditorPaths.xml
new file mode 100644
index 0000000000..b92927fd53
--- /dev/null
+++ b/doc/classes/EditorPaths.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="EditorPaths" inherits="Object" version="4.0">
+ <brief_description>
+ </brief_description>
+ <description>
+ </description>
+ <tutorials>
+ </tutorials>
+ <methods>
+ <method name="get_cache_dir" qualifiers="const">
+ <return type="String">
+ </return>
+ <description>
+ </description>
+ </method>
+ <method name="get_config_dir" qualifiers="const">
+ <return type="String">
+ </return>
+ <description>
+ </description>
+ </method>
+ <method name="get_data_dir" qualifiers="const">
+ <return type="String">
+ </return>
+ <description>
+ </description>
+ </method>
+ <method name="get_self_contained_file" qualifiers="const">
+ <return type="String">
+ </return>
+ <description>
+ </description>
+ </method>
+ <method name="get_settings_dir" qualifiers="const">
+ <return type="String">
+ </return>
+ <description>
+ </description>
+ </method>
+ <method name="is_self_contained" qualifiers="const">
+ <return type="bool">
+ </return>
+ <description>
+ </description>
+ </method>
+ </methods>
+ <constants>
+ </constants>
+</class>
diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml
index 016d0128eb..e732223516 100644
--- a/doc/classes/EditorSettings.xml
+++ b/doc/classes/EditorSettings.xml
@@ -124,15 +124,6 @@
Returns the value of the setting specified by [code]name[/code]. This is equivalent to using [method Object.get] on the EditorSettings instance.
</description>
</method>
- <method name="get_settings_dir" qualifiers="const">
- <return type="String">
- </return>
- <description>
- Gets the global settings path for the engine. Inside this path, you can find some standard paths such as:
- [code]settings/tmp[/code] - Used for temporary storage of files
- [code]settings/templates[/code] - Where export templates are located
- </description>
- </method>
<method name="has_setting" qualifiers="const">
<return type="bool">
</return>
diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml
index f6ade5583e..73899daaea 100644
--- a/doc/classes/ProjectSettings.xml
+++ b/doc/classes/ProjectSettings.xml
@@ -1502,6 +1502,16 @@
<member name="rendering/reflections/sky_reflections/texture_array_reflections.mobile" type="bool" setter="" getter="" default="false">
Lower-end override for [member rendering/reflections/sky_reflections/texture_array_reflections] on mobile devices, due to performance concerns or driver support.
</member>
+ <member name="rendering/shader_compiler/shader_cache/compress" type="bool" setter="" getter="" default="true">
+ </member>
+ <member name="rendering/shader_compiler/shader_cache/enabled" type="bool" setter="" getter="" default="true">
+ </member>
+ <member name="rendering/shader_compiler/shader_cache/strip_debug" type="bool" setter="" getter="" default="false">
+ </member>
+ <member name="rendering/shader_compiler/shader_cache/strip_debug.release" type="bool" setter="" getter="" default="true">
+ </member>
+ <member name="rendering/shader_compiler/shader_cache/use_zstd_compression" type="bool" setter="" getter="" default="true">
+ </member>
<member name="rendering/shading/overrides/force_blinn_over_ggx" type="bool" setter="" getter="" default="false">
If [code]true[/code], uses faster but lower-quality Blinn model to generate blurred reflections instead of the GGX model.
</member>
diff --git a/doc/classes/VisualShaderNodeBillboard.xml b/doc/classes/VisualShaderNodeBillboard.xml
new file mode 100644
index 0000000000..53bcfa7b5c
--- /dev/null
+++ b/doc/classes/VisualShaderNodeBillboard.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="VisualShaderNodeBillboard" inherits="VisualShaderNode" version="4.0">
+ <brief_description>
+ A node that controls how the object faces the camera to be used within the visual shader graph.
+ </brief_description>
+ <description>
+ The output port of this node needs to be connected to [code]Model View Matrix[/code] port of [VisualShaderNodeOutput].
+ </description>
+ <tutorials>
+ </tutorials>
+ <methods>
+ </methods>
+ <members>
+ <member name="billboard_type" type="int" setter="set_billboard_type" getter="get_billboard_type" enum="VisualShaderNodeBillboard.BillboardType" default="1">
+ Controls how the object faces the camera. See [enum BillboardType].
+ </member>
+ <member name="keep_scale" type="bool" setter="set_keep_scale_enabled" getter="is_keep_scale_enabled" default="false">
+ If [code]true[/code], the shader will keep the scale set for the mesh. Otherwise, the scale is lost when billboarding.
+ </member>
+ </members>
+ <constants>
+ <constant name="BILLBOARD_TYPE_DISABLED" value="0" enum="BillboardType">
+ Billboarding is disabled and the node does nothing.
+ </constant>
+ <constant name="BILLBOARD_TYPE_ENABLED" value="1" enum="BillboardType">
+ A standard billboarding algorithm is enabled.
+ </constant>
+ <constant name="BILLBOARD_TYPE_FIXED_Y" value="2" enum="BillboardType">
+ A billboarding algorithm to rotate around Y-axis is enabled.
+ </constant>
+ <constant name="BILLBOARD_TYPE_PARTICLES" value="3" enum="BillboardType">
+ A billboarding algorithm designed to use on particles is enabled.
+ </constant>
+ <constant name="BILLBOARD_TYPE_MAX" value="4" enum="BillboardType">
+ Represents the size of the [enum BillboardType] enum.
+ </constant>
+ </constants>
+</class>
diff --git a/drivers/unix/dir_access_unix.cpp b/drivers/unix/dir_access_unix.cpp
index 5abe5d2c87..a2c9bae852 100644
--- a/drivers/unix/dir_access_unix.cpp
+++ b/drivers/unix/dir_access_unix.cpp
@@ -460,7 +460,7 @@ uint64_t DirAccessUnix::get_space_left() {
return 0;
};
- return vfs.f_bfree * vfs.f_bsize;
+ return (uint64_t)vfs.f_bavail * (uint64_t)vfs.f_frsize;
#else
// FIXME: Implement this.
return 0;
diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp
index 87ac16fdcf..97ba5d7a5f 100644
--- a/editor/code_editor.cpp
+++ b/editor/code_editor.cpp
@@ -302,7 +302,7 @@ void FindReplaceBar::_replace_all() {
matches_label->add_theme_color_override("font_color", rc > 0 ? get_theme_color("font_color", "Label") : get_theme_color("error_color", "Editor"));
matches_label->set_text(vformat(TTR("%d replaced."), rc));
- text_editor->call_deferred("connect", "text_changed", Callable(this, "_editor_text_changed"));
+ text_editor->call_deferred("connect", "text_changed", callable_mp(this, &FindReplaceBar::_editor_text_changed));
results_count = -1;
}
diff --git a/editor/editor_audio_buses.cpp b/editor/editor_audio_buses.cpp
index 466ca70130..4317760379 100644
--- a/editor/editor_audio_buses.cpp
+++ b/editor/editor_audio_buses.cpp
@@ -173,6 +173,9 @@ void EditorAudioBus::_notification(int p_what) {
bypass->set_icon(get_theme_icon("AudioBusBypass", "EditorIcons"));
bus_options->set_icon(get_theme_icon("GuiTabMenuHl", "EditorIcons"));
+
+ audio_value_preview_box->add_theme_color_override("font_color", get_theme_color("font_color", "TooltipLabel"));
+ audio_value_preview_box->add_theme_style_override("panel", get_theme_stylebox("panel", "TooltipPanel"));
} break;
case NOTIFICATION_MOUSE_EXIT:
case NOTIFICATION_DRAG_END: {
@@ -379,15 +382,24 @@ void EditorAudioBus::_show_value(float slider_value) {
db = _normalized_volume_to_scaled_db(slider_value);
}
- String text = vformat("%10.1f dB", db);
+ String text;
+ if (Math::is_zero_approx(Math::snapped(db, 0.1))) {
+ // Prevent displaying `-0.0 dB` and show ` 0.0 dB` instead.
+ // The leading space makes the text visually line up with its positive/negative counterparts.
+ text = " 0.0 dB";
+ } else {
+ // Show an explicit `+` sign if positive.
+ text = vformat("%+.1f dB", db);
+ }
+ // Also set the preview text as a standard Control tooltip.
+ // This way, it can be seen when the slider is merely hovered (instead of dragged).
slider->set_tooltip(text);
audio_value_preview_label->set_text(text);
- Vector2 slider_size = slider->get_size();
- Vector2 slider_position = slider->get_global_position();
- float left_padding = 5.0f;
- float vert_padding = 10.0f;
- Vector2 box_position = Vector2(slider_size.x + left_padding, (slider_size.y - vert_padding) * (1.0f - slider->get_value()) - vert_padding);
+ const Vector2 slider_size = slider->get_size();
+ const Vector2 slider_position = slider->get_global_position();
+ const float vert_padding = 10.0f;
+ const Vector2 box_position = Vector2(slider_size.x, (slider_size.y - vert_padding) * (1.0f - slider->get_value()) - vert_padding);
audio_value_preview_box->set_position(slider_position + box_position);
audio_value_preview_box->set_size(audio_value_preview_label->get_size());
if (slider->has_focus() && !audio_value_preview_box->is_visible()) {
@@ -830,14 +842,13 @@ EditorAudioBus::EditorAudioBus(EditorAudioBuses *p_buses, bool p_is_master) {
audio_value_preview_label->set_v_size_flags(SIZE_EXPAND_FILL);
audio_value_preview_label->set_h_size_flags(SIZE_EXPAND_FILL);
audio_value_preview_label->set_mouse_filter(MOUSE_FILTER_PASS);
+ audio_value_preview_box->add_theme_color_override("font_color", get_theme_color("font_color", "TooltipLabel"));
audioprev_hbc->add_child(audio_value_preview_label);
slider->add_child(audio_value_preview_box);
audio_value_preview_box->set_as_top_level(true);
- Ref<StyleBoxFlat> panel_style = memnew(StyleBoxFlat);
- panel_style->set_bg_color(Color(0.0f, 0.0f, 0.0f, 0.8f));
- audio_value_preview_box->add_theme_style_override("panel", panel_style);
+ audio_value_preview_box->add_theme_style_override("panel", get_theme_stylebox("panel", "TooltipPanel"));
audio_value_preview_box->set_mouse_filter(MOUSE_FILTER_PASS);
audio_value_preview_box->hide();
@@ -925,7 +936,7 @@ EditorAudioBus::EditorAudioBus(EditorAudioBuses *p_buses, bool p_is_master) {
bus_options->set_shortcut_context(this);
bus_options->set_h_size_flags(SIZE_SHRINK_END);
bus_options->set_anchor(SIDE_RIGHT, 0.0);
- bus_options->set_tooltip(TTR("Bus options"));
+ bus_options->set_tooltip(TTR("Bus Options"));
hbc->add_child(bus_options);
bus_popup = bus_options->get_popup();
@@ -1400,7 +1411,7 @@ void EditorAudioMeterNotches::_bind_methods() {
void EditorAudioMeterNotches::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_THEME_CHANGED: {
- notch_color = EditorSettings::get_singleton()->is_dark_theme() ? Color(1, 1, 1) : Color(0, 0, 0);
+ notch_color = get_theme_color("font_color", "Editor");
} break;
case NOTIFICATION_DRAW: {
_draw_audio_notches();
@@ -1416,13 +1427,13 @@ void EditorAudioMeterNotches::_draw_audio_notches() {
for (int i = 0; i < notches.size(); i++) {
AudioNotch n = notches[i];
draw_line(Vector2(0, (1.0f - n.relative_position) * (get_size().y - btm_padding - top_padding) + top_padding),
- Vector2(line_length, (1.0f - n.relative_position) * (get_size().y - btm_padding - top_padding) + top_padding),
+ Vector2(line_length * EDSCALE, (1.0f - n.relative_position) * (get_size().y - btm_padding - top_padding) + top_padding),
notch_color,
- 1);
+ Math::round(EDSCALE));
if (n.render_db_value) {
draw_string(font,
- Vector2(line_length + label_space,
+ Vector2((line_length + label_space) * EDSCALE,
(1.0f - n.relative_position) * (get_size().y - btm_padding - top_padding) + (font_height / 4) + top_padding),
String::num(Math::abs(n.db_value)) + "dB",
HALIGN_LEFT, -1, font_size,
@@ -1432,5 +1443,5 @@ void EditorAudioMeterNotches::_draw_audio_notches() {
}
EditorAudioMeterNotches::EditorAudioMeterNotches() {
- notch_color = EditorSettings::get_singleton()->is_dark_theme() ? Color(1, 1, 1) : Color(0, 0, 0);
+ notch_color = get_theme_color("font_color", "Editor");
}
diff --git a/editor/editor_audio_buses.h b/editor/editor_audio_buses.h
index 8dfc2137ef..0fbda8ece9 100644
--- a/editor/editor_audio_buses.h
+++ b/editor/editor_audio_buses.h
@@ -243,10 +243,10 @@ private:
List<AudioNotch> notches;
public:
- float line_length = 5.0f;
- float label_space = 2.0f;
- float btm_padding = 9.0f;
- float top_padding = 5.0f;
+ const float line_length = 5.0f;
+ const float label_space = 2.0f;
+ const float btm_padding = 9.0f;
+ const float top_padding = 5.0f;
Color notch_color;
void add_notch(float p_normalized_offset, float p_db_value, bool p_render_value = false);
diff --git a/editor/editor_export.cpp b/editor/editor_export.cpp
index b3755bef80..40313fbeff 100644
--- a/editor/editor_export.cpp
+++ b/editor/editor_export.cpp
@@ -1065,7 +1065,7 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> &
}
} else {
// Use default text server data.
- String icu_data_file = EditorSettings::get_singleton()->get_cache_dir().plus_file("tmp_icu_data");
+ String icu_data_file = EditorPaths::get_singleton()->get_cache_dir().plus_file("tmp_icu_data");
TS->save_support_data(icu_data_file);
Vector<uint8_t> array = FileAccess::get_file_as_array(icu_data_file);
err = p_func(p_udata, ts_data, array, idx, total, enc_in_filters, enc_ex_filters, key);
@@ -1078,7 +1078,7 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> &
}
String config_file = "project.binary";
- String engine_cfb = EditorSettings::get_singleton()->get_cache_dir().plus_file("tmp" + config_file);
+ String engine_cfb = EditorPaths::get_singleton()->get_cache_dir().plus_file("tmp" + config_file);
ProjectSettings::get_singleton()->save_custom(engine_cfb, custom_map, custom_list);
Vector<uint8_t> data = FileAccess::get_file_as_array(engine_cfb);
DirAccess::remove_file_or_error(engine_cfb);
@@ -1100,9 +1100,9 @@ Error EditorExportPlatform::save_pack(const Ref<EditorExportPreset> &p_preset, c
// Create the temporary export directory if it doesn't exist.
DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
- da->make_dir_recursive(EditorSettings::get_singleton()->get_cache_dir());
+ da->make_dir_recursive(EditorPaths::get_singleton()->get_cache_dir());
- String tmppath = EditorSettings::get_singleton()->get_cache_dir().plus_file("packtmp");
+ String tmppath = EditorPaths::get_singleton()->get_cache_dir().plus_file("packtmp");
FileAccess *ftmp = FileAccess::open(tmppath, FileAccess::WRITE);
ERR_FAIL_COND_V_MSG(!ftmp, ERR_CANT_CREATE, "Cannot create file '" + tmppath + "'.");
@@ -1984,7 +1984,7 @@ void EditorExportTextSceneToBinaryPlugin::_export_file(const String &p_path, con
if (!convert) {
return;
}
- String tmp_path = EditorSettings::get_singleton()->get_cache_dir().plus_file("tmpfile.res");
+ String tmp_path = EditorPaths::get_singleton()->get_cache_dir().plus_file("tmpfile.res");
Error err = ResourceFormatLoaderText::convert_file_to_binary(p_path, tmp_path);
if (err != OK) {
DirAccess::remove_file_or_error(tmp_path);
diff --git a/editor/editor_feature_profile.cpp b/editor/editor_feature_profile.cpp
index bd00d86ec8..529bbe97eb 100644
--- a/editor/editor_feature_profile.cpp
+++ b/editor/editor_feature_profile.cpp
@@ -118,6 +118,18 @@ bool EditorFeatureProfile::has_class_properties_disabled(const StringName &p_cla
return disabled_properties.has(p_class);
}
+void EditorFeatureProfile::set_item_collapsed(const StringName &p_class, bool p_collapsed) {
+ if (p_collapsed) {
+ collapsed_classes.insert(p_class);
+ } else {
+ collapsed_classes.erase(p_class);
+ }
+}
+
+bool EditorFeatureProfile::is_item_collapsed(const StringName &p_class) const {
+ return collapsed_classes.has(p_class);
+}
+
void EditorFeatureProfile::set_disable_feature(Feature p_feature, bool p_disable) {
ERR_FAIL_INDEX(p_feature, FEATURE_MAX);
features_disabled[p_feature] = p_disable;
@@ -484,6 +496,9 @@ void EditorFeatureProfileManager::_fill_classes_from(TreeItem *p_parent, const S
class_item->set_selectable(0, true);
class_item->set_metadata(0, p_class);
+ bool collapsed = edited->is_item_collapsed(p_class);
+ class_item->set_collapsed(collapsed);
+
if (p_class == p_selected) {
class_item->select(0);
}
@@ -596,6 +611,26 @@ void EditorFeatureProfileManager::_class_list_item_edited() {
}
}
+void EditorFeatureProfileManager::_class_list_item_collapsed(Object *p_item) {
+ if (updating_features) {
+ return;
+ }
+
+ TreeItem *item = Object::cast_to<TreeItem>(p_item);
+ if (!item) {
+ return;
+ }
+
+ Variant md = item->get_metadata(0);
+ if (md.get_type() != Variant::STRING && md.get_type() != Variant::STRING_NAME) {
+ return;
+ }
+
+ String class_name = md;
+ bool collapsed = item->is_collapsed();
+ edited->set_item_collapsed(class_name, collapsed);
+}
+
void EditorFeatureProfileManager::_property_item_edited() {
if (updating_features) {
return;
@@ -853,6 +888,7 @@ EditorFeatureProfileManager::EditorFeatureProfileManager() {
class_list->set_edit_checkbox_cell_only_when_checkbox_is_pressed(true);
class_list->connect("cell_selected", callable_mp(this, &EditorFeatureProfileManager::_class_list_item_selected));
class_list->connect("item_edited", callable_mp(this, &EditorFeatureProfileManager::_class_list_item_edited), varray(), CONNECT_DEFERRED);
+ class_list->connect("item_collapsed", callable_mp(this, &EditorFeatureProfileManager::_class_list_item_collapsed));
// It will be displayed once the user creates or chooses a profile.
class_list_vbc->hide();
diff --git a/editor/editor_feature_profile.h b/editor/editor_feature_profile.h
index 01e6a6a142..9561e11c81 100644
--- a/editor/editor_feature_profile.h
+++ b/editor/editor_feature_profile.h
@@ -60,6 +60,8 @@ private:
Set<StringName> disabled_editors;
Map<StringName, Set<StringName>> disabled_properties;
+ Set<StringName> collapsed_classes;
+
bool features_disabled[FEATURE_MAX];
static const char *feature_names[FEATURE_MAX];
static const char *feature_identifiers[FEATURE_MAX];
@@ -80,6 +82,9 @@ public:
bool is_class_property_disabled(const StringName &p_class, const StringName &p_property) const;
bool has_class_properties_disabled(const StringName &p_class) const;
+ void set_item_collapsed(const StringName &p_class, bool p_collapsed);
+ bool is_item_collapsed(const StringName &p_class) const;
+
void set_disable_feature(Feature p_feature, bool p_disable);
bool is_feature_disabled(Feature p_feature) const;
@@ -151,6 +156,7 @@ class EditorFeatureProfileManager : public AcceptDialog {
void _class_list_item_selected();
void _class_list_item_edited();
+ void _class_list_item_collapsed(Object *p_item);
void _property_item_edited();
void _save_and_update();
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index 72963012d6..aa632b90ad 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -80,6 +80,7 @@
#include "editor/editor_inspector.h"
#include "editor/editor_layouts_dialog.h"
#include "editor/editor_log.h"
+#include "editor/editor_paths.h"
#include "editor/editor_plugin.h"
#include "editor/editor_properties.h"
#include "editor/editor_resource_picker.h"
@@ -1457,7 +1458,7 @@ void EditorNode::_save_scene_with_preview(String p_file, int p_idx) {
img->convert(Image::FORMAT_RGB8);
//save thumbnail directly, as thumbnailer may not update due to actual scene not changing md5
- String temp_path = EditorSettings::get_singleton()->get_cache_dir();
+ String temp_path = EditorPaths::get_singleton()->get_cache_dir();
String cache_base = ProjectSettings::get_singleton()->globalize_path(p_file).md5_text();
cache_base = temp_path.plus_file("resthumb-" + cache_base);
@@ -2745,10 +2746,10 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) {
settings_config_dialog->popup_edit_settings();
} break;
case SETTINGS_EDITOR_DATA_FOLDER: {
- OS::get_singleton()->shell_open(String("file://") + EditorSettings::get_singleton()->get_data_dir());
+ OS::get_singleton()->shell_open(String("file://") + EditorPaths::get_singleton()->get_data_dir());
} break;
case SETTINGS_EDITOR_CONFIG_FOLDER: {
- OS::get_singleton()->shell_open(String("file://") + EditorSettings::get_singleton()->get_settings_dir());
+ OS::get_singleton()->shell_open(String("file://") + EditorPaths::get_singleton()->get_settings_dir());
} break;
case SETTINGS_MANAGE_EXPORT_TEMPLATES: {
export_template_manager->popup_manager();
@@ -2892,7 +2893,7 @@ void EditorNode::_exit_editor() {
_save_docks();
// Dim the editor window while it's quitting to make it clearer that it's busy
- dim_editor(true, true);
+ dim_editor(true);
get_tree()->quit();
}
@@ -3727,10 +3728,15 @@ bool EditorNode::is_scene_in_use(const String &p_path) {
return false;
}
+void EditorNode::register_editor_paths(bool p_for_project_manager) {
+ EditorPaths::create(p_for_project_manager);
+}
+
void EditorNode::register_editor_types() {
ResourceLoader::set_timestamp_on_load(true);
ResourceSaver::set_timestamp_on_save(true);
+ ClassDB::register_class<EditorPaths>();
ClassDB::register_class<EditorPlugin>();
ClassDB::register_class<EditorTranslationParserPlugin>();
ClassDB::register_class<EditorImportPlugin>();
@@ -3774,6 +3780,9 @@ void EditorNode::register_editor_types() {
void EditorNode::unregister_editor_types() {
_init_callbacks.clear();
+ if (EditorPaths::get_singleton()) {
+ EditorPaths::free();
+ }
}
void EditorNode::stop_child_process(OS::ProcessID p_pid) {
@@ -5396,15 +5405,9 @@ void EditorNode::_open_imported() {
load_scene(open_import_request, true, false, true, true);
}
-void EditorNode::dim_editor(bool p_dimming, bool p_force_dim) {
- // Dimming can be forced regardless of the editor setting, which is useful when quitting the editor.
- if ((p_force_dim || EditorSettings::get_singleton()->get("interface/editor/dim_editor_on_dialog_popup")) && p_dimming) {
- dimmed = true;
- gui_base->set_modulate(Color(0.5, 0.5, 0.5));
- } else {
- dimmed = false;
- gui_base->set_modulate(Color(1, 1, 1));
- }
+void EditorNode::dim_editor(bool p_dimming) {
+ dimmed = p_dimming;
+ gui_base->set_modulate(p_dimming ? Color(0.5, 0.5, 0.5) : Color(1, 1, 1));
}
bool EditorNode::is_editor_dimmed() const {
diff --git a/editor/editor_node.h b/editor/editor_node.h
index 9824702d7b..9625b318e0 100644
--- a/editor/editor_node.h
+++ b/editor/editor_node.h
@@ -798,6 +798,7 @@ public:
Error export_preset(const String &p_preset, const String &p_path, bool p_debug, bool p_pack_only);
+ static void register_editor_paths(bool p_for_project_manager);
static void register_editor_types();
static void unregister_editor_types();
@@ -855,7 +856,7 @@ public:
void notify_settings_changed();
- void dim_editor(bool p_dimming, bool p_force_dim = false);
+ void dim_editor(bool p_dimming);
bool is_editor_dimmed() const;
void edit_current() { _edit_current(); };
diff --git a/editor/editor_paths.cpp b/editor/editor_paths.cpp
new file mode 100644
index 0000000000..96469d3143
--- /dev/null
+++ b/editor/editor_paths.cpp
@@ -0,0 +1,156 @@
+/*************************************************************************/
+/* editor_paths.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 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_paths.h"
+#include "core/os/dir_access.h"
+#include "core/os/os.h"
+
+EditorPaths *EditorPaths::singleton = nullptr;
+
+bool EditorPaths::are_paths_valid() const {
+ return paths_valid;
+}
+
+String EditorPaths::get_settings_dir() const {
+ return settings_dir;
+}
+String EditorPaths::get_data_dir() const {
+ return data_dir;
+}
+String EditorPaths::get_config_dir() const {
+ return config_dir;
+}
+String EditorPaths::get_cache_dir() const {
+ return cache_dir;
+}
+bool EditorPaths::is_self_contained() const {
+ return self_contained;
+}
+String EditorPaths::get_self_contained_file() const {
+ return self_contained_file;
+}
+
+void EditorPaths::create(bool p_for_project_manager) {
+ ERR_FAIL_COND(singleton != nullptr);
+ memnew(EditorPaths(p_for_project_manager));
+}
+void EditorPaths::free() {
+ ERR_FAIL_COND(singleton == nullptr);
+ memdelete(singleton);
+}
+
+void EditorPaths::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("get_settings_dir"), &EditorPaths::get_settings_dir);
+ ClassDB::bind_method(D_METHOD("get_data_dir"), &EditorPaths::get_data_dir);
+ ClassDB::bind_method(D_METHOD("get_config_dir"), &EditorPaths::get_config_dir);
+ ClassDB::bind_method(D_METHOD("get_cache_dir"), &EditorPaths::get_cache_dir);
+ ClassDB::bind_method(D_METHOD("is_self_contained"), &EditorPaths::is_self_contained);
+ ClassDB::bind_method(D_METHOD("get_self_contained_file"), &EditorPaths::get_self_contained_file);
+}
+
+EditorPaths::EditorPaths(bool p_for_project_mamanger) {
+ singleton = this;
+
+ String exe_path = OS::get_singleton()->get_executable_path().get_base_dir();
+ {
+ DirAccessRef d = DirAccess::create_for_path(exe_path);
+
+ if (d->file_exists(exe_path + "/._sc_")) {
+ self_contained = true;
+ self_contained_file = exe_path + "/._sc_";
+ } else if (d->file_exists(exe_path + "/_sc_")) {
+ self_contained = true;
+ self_contained_file = exe_path + "/_sc_";
+ }
+ }
+
+ String data_path;
+ String config_path;
+ String cache_path;
+
+ if (self_contained) {
+ // editor is self contained, all in same folder
+ data_path = exe_path;
+ data_dir = data_path.plus_file("editor_data");
+ config_path = exe_path;
+ config_dir = data_dir;
+ cache_path = exe_path;
+ cache_dir = data_dir.plus_file("cache");
+ } else {
+ // Typically XDG_DATA_HOME or %APPDATA%
+ data_path = OS::get_singleton()->get_data_path();
+ data_dir = data_path.plus_file(OS::get_singleton()->get_godot_dir_name());
+ // Can be different from data_path e.g. on Linux or macOS
+ config_path = OS::get_singleton()->get_config_path();
+ config_dir = config_path.plus_file(OS::get_singleton()->get_godot_dir_name());
+ // Can be different from above paths, otherwise a subfolder of data_dir
+ cache_path = OS::get_singleton()->get_cache_path();
+ if (cache_path == data_path) {
+ cache_dir = data_dir.plus_file("cache");
+ } else {
+ cache_dir = cache_path.plus_file(OS::get_singleton()->get_godot_dir_name());
+ }
+ }
+
+ paths_valid = (data_path != "" && config_path != "" && cache_path != "");
+
+ if (paths_valid) {
+ DirAccessRef dir = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+ if (dir->change_dir(data_dir) != OK) {
+ dir->make_dir_recursive(data_dir);
+ if (dir->change_dir(data_dir) != OK) {
+ ERR_PRINT("Cannot create data directory!");
+ paths_valid = false;
+ }
+ }
+
+ // Validate/create cache dir
+
+ if (dir->change_dir(EditorPaths::get_singleton()->get_cache_dir()) != OK) {
+ dir->make_dir_recursive(cache_dir);
+ if (dir->change_dir(cache_dir) != OK) {
+ ERR_PRINT("Cannot create cache directory!");
+ }
+ }
+
+ if (p_for_project_mamanger) {
+ Engine::get_singleton()->set_shader_cache_path(get_data_dir());
+ } else {
+ DirAccessRef dir2 = DirAccess::open("res://");
+ if (dir2->change_dir(".godot") != OK) { //ensure the .godot subdir exists
+ if (dir2->make_dir(".godot") != OK) {
+ ERR_PRINT("Cannot create res://.godot directory!");
+ }
+ }
+
+ Engine::get_singleton()->set_shader_cache_path("res://.godot");
+ }
+ }
+}
diff --git a/editor/editor_paths.h b/editor/editor_paths.h
new file mode 100644
index 0000000000..096174943d
--- /dev/null
+++ b/editor/editor_paths.h
@@ -0,0 +1,72 @@
+/*************************************************************************/
+/* editor_paths.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2021 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 EDITORPATHS_H
+#define EDITORPATHS_H
+
+#include "core/config/engine.h"
+
+class EditorPaths : public Object {
+ GDCLASS(EditorPaths, Object)
+
+ bool paths_valid = false;
+ String settings_dir;
+ String data_dir; //editor data dir
+ String config_dir; //editor config dir
+ String cache_dir; //editor cache dir
+ bool self_contained = false; //true if running self contained
+ String self_contained_file; //self contained file with configuration
+
+ static EditorPaths *singleton;
+
+protected:
+ static void _bind_methods();
+
+public:
+ bool are_paths_valid() const;
+
+ String get_settings_dir() const;
+ String get_data_dir() const;
+ String get_config_dir() const;
+ String get_cache_dir() const;
+ bool is_self_contained() const;
+ String get_self_contained_file() const;
+
+ static EditorPaths *get_singleton() {
+ return singleton;
+ }
+
+ static void create(bool p_for_project_manager);
+ static void free();
+
+ EditorPaths(bool p_for_project_mamanger = false);
+};
+
+#endif // EDITORPATHS_H
diff --git a/editor/editor_plugin.cpp b/editor/editor_plugin.cpp
index 6b96cb0f5c..c5097a17a5 100644
--- a/editor/editor_plugin.cpp
+++ b/editor/editor_plugin.cpp
@@ -32,6 +32,7 @@
#include "editor/editor_export.h"
#include "editor/editor_node.h"
+#include "editor/editor_paths.h"
#include "editor/editor_settings.h"
#include "editor/filesystem_dock.h"
#include "editor/project_settings_editor.h"
@@ -257,6 +258,9 @@ EditorSelection *EditorInterface::get_selection() {
Ref<EditorSettings> EditorInterface::get_editor_settings() {
return EditorSettings::get_singleton();
}
+EditorPaths *EditorInterface::get_editor_paths() {
+ return EditorPaths::get_singleton();
+}
EditorResourcePreview *EditorInterface::get_resource_previewer() {
return EditorResourcePreview::get_singleton();
@@ -335,6 +339,7 @@ void EditorInterface::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_selected_path"), &EditorInterface::get_selected_path);
ClassDB::bind_method(D_METHOD("get_current_path"), &EditorInterface::get_current_path);
ClassDB::bind_method(D_METHOD("get_file_system_dock"), &EditorInterface::get_file_system_dock);
+ ClassDB::bind_method(D_METHOD("get_editor_paths"), &EditorInterface::get_editor_paths);
ClassDB::bind_method(D_METHOD("set_plugin_enabled", "plugin", "enabled"), &EditorInterface::set_plugin_enabled);
ClassDB::bind_method(D_METHOD("is_plugin_enabled", "plugin"), &EditorInterface::is_plugin_enabled);
diff --git a/editor/editor_plugin.h b/editor/editor_plugin.h
index 37412e5ebe..3f72e468b2 100644
--- a/editor/editor_plugin.h
+++ b/editor/editor_plugin.h
@@ -54,6 +54,7 @@ class EditorNode3DGizmoPlugin;
class EditorResourcePreview;
class EditorFileSystem;
class EditorToolAddons;
+class EditorPaths;
class FileSystemDock;
class ScriptEditor;
@@ -95,6 +96,7 @@ public:
EditorSelection *get_selection();
//EditorImportExport *get_import_export();
Ref<EditorSettings> get_editor_settings();
+ EditorPaths *get_editor_paths();
EditorResourcePreview *get_resource_previewer();
EditorFileSystem *get_resource_file_system();
diff --git a/editor/editor_resource_preview.cpp b/editor/editor_resource_preview.cpp
index 138830cdc6..35cf08b4d7 100644
--- a/editor/editor_resource_preview.cpp
+++ b/editor/editor_resource_preview.cpp
@@ -241,7 +241,7 @@ void EditorResourcePreview::_thread() {
_preview_ready(item.path + ":" + itos(item.resource->hash_edited_version()), texture, small_texture, item.id, item.function, item.userdata);
} else {
- String temp_path = EditorSettings::get_singleton()->get_cache_dir();
+ String temp_path = EditorPaths::get_singleton()->get_cache_dir();
String cache_base = ProjectSettings::get_singleton()->globalize_path(item.path).md5_text();
cache_base = temp_path.plus_file("resthumb-" + cache_base);
diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp
index eb8ad9bac4..28b01c0d18 100644
--- a/editor/editor_settings.cpp
+++ b/editor/editor_settings.cpp
@@ -419,7 +419,6 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) {
hints["interface/editor/main_font_bold"] = PropertyInfo(Variant::STRING, "interface/editor/main_font_bold", PROPERTY_HINT_GLOBAL_FILE, "*.ttf,*.otf", PROPERTY_USAGE_DEFAULT);
_initial_set("interface/editor/code_font", "");
hints["interface/editor/code_font"] = PropertyInfo(Variant::STRING, "interface/editor/code_font", PROPERTY_HINT_GLOBAL_FILE, "*.ttf,*.otf", PROPERTY_USAGE_DEFAULT);
- _initial_set("interface/editor/dim_editor_on_dialog_popup", true);
_initial_set("interface/editor/low_processor_mode_sleep_usec", 6900); // ~144 FPS
hints["interface/editor/low_processor_mode_sleep_usec"] = PropertyInfo(Variant::FLOAT, "interface/editor/low_processor_mode_sleep_usec", PROPERTY_HINT_RANGE, "1,100000,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED);
_initial_set("interface/editor/unfocused_low_processor_mode_sleep_usec", 50000); // 20 FPS
@@ -902,67 +901,26 @@ void EditorSettings::create() {
return; //pointless
}
- DirAccess *dir = nullptr;
-
- String data_path;
- String data_dir;
- String config_path;
- String config_dir;
- String cache_path;
- String cache_dir;
-
Ref<ConfigFile> extra_config = memnew(ConfigFile);
- String exe_path = OS::get_singleton()->get_executable_path().get_base_dir();
- DirAccess *d = DirAccess::create_for_path(exe_path);
- bool self_contained = false;
-
- if (d->file_exists(exe_path + "/._sc_")) {
- self_contained = true;
- Error err = extra_config->load(exe_path + "/._sc_");
- if (err != OK) {
- ERR_PRINT("Can't load config from path '" + exe_path + "/._sc_'.");
- }
- } else if (d->file_exists(exe_path + "/_sc_")) {
- self_contained = true;
- Error err = extra_config->load(exe_path + "/_sc_");
+ if (EditorPaths::get_singleton()->is_self_contained()) {
+ Error err = extra_config->load(EditorPaths::get_singleton()->get_self_contained_file());
if (err != OK) {
- ERR_PRINT("Can't load config from path '" + exe_path + "/_sc_'.");
+ ERR_PRINT("Can't load extra config from path :" + EditorPaths::get_singleton()->get_self_contained_file());
}
}
- memdelete(d);
- if (self_contained) {
- // editor is self contained, all in same folder
- data_path = exe_path;
- data_dir = data_path.plus_file("editor_data");
- config_path = exe_path;
- config_dir = data_dir;
- cache_path = exe_path;
- cache_dir = data_dir.plus_file("cache");
- } else {
- // Typically XDG_DATA_HOME or %APPDATA%
- data_path = OS::get_singleton()->get_data_path();
- data_dir = data_path.plus_file(OS::get_singleton()->get_godot_dir_name());
- // Can be different from data_path e.g. on Linux or macOS
- config_path = OS::get_singleton()->get_config_path();
- config_dir = config_path.plus_file(OS::get_singleton()->get_godot_dir_name());
- // Can be different from above paths, otherwise a subfolder of data_dir
- cache_path = OS::get_singleton()->get_cache_path();
- if (cache_path == data_path) {
- cache_dir = data_dir.plus_file("cache");
- } else {
- cache_dir = cache_path.plus_file(OS::get_singleton()->get_godot_dir_name());
- }
- }
+ DirAccess *dir = nullptr;
ClassDB::register_class<EditorSettings>(); //otherwise it can't be unserialized
String config_file_path;
- if (data_path != "" && config_path != "" && cache_path != "") {
+ if (EditorPaths::get_singleton()->are_paths_valid()) {
// Validate/create data dir and subdirectories
+ String data_dir = EditorPaths::get_singleton()->get_data_dir();
+
dir = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
if (dir->change_dir(data_dir) != OK) {
dir->make_dir_recursive(data_dir);
@@ -979,22 +937,11 @@ void EditorSettings::create() {
dir->change_dir("..");
}
- // Validate/create cache dir
-
- if (dir->change_dir(cache_dir) != OK) {
- dir->make_dir_recursive(cache_dir);
- if (dir->change_dir(cache_dir) != OK) {
- ERR_PRINT("Cannot create cache directory!");
- memdelete(dir);
- goto fail;
- }
- }
-
// Validate/create config dir and subdirectories
- if (dir->change_dir(config_dir) != OK) {
- dir->make_dir_recursive(config_dir);
- if (dir->change_dir(config_dir) != OK) {
+ if (dir->change_dir(EditorPaths::get_singleton()->get_config_dir()) != OK) {
+ dir->make_dir_recursive(EditorPaths::get_singleton()->get_config_dir());
+ if (dir->change_dir(EditorPaths::get_singleton()->get_config_dir()) != OK) {
ERR_PRINT("Cannot create config directory!");
memdelete(dir);
goto fail;
@@ -1035,7 +982,7 @@ void EditorSettings::create() {
// Validate editor config file
String config_file_name = "editor_settings-" + itos(VERSION_MAJOR) + ".tres";
- config_file_path = config_dir.plus_file(config_file_name);
+ config_file_path = EditorPaths::get_singleton()->get_config_dir().plus_file(config_file_name);
if (!dir->file_exists(config_file_name)) {
memdelete(dir);
goto fail;
@@ -1052,9 +999,6 @@ void EditorSettings::create() {
singleton->save_changed_setting = true;
singleton->config_file_path = config_file_path;
- singleton->settings_dir = config_dir;
- singleton->data_dir = data_dir;
- singleton->cache_dir = cache_dir;
print_verbose("EditorSettings: Load OK!");
@@ -1069,6 +1013,8 @@ void EditorSettings::create() {
fail:
// patch init projects
+ String exe_path = OS::get_singleton()->get_executable_path().get_base_dir();
+
if (extra_config->has_section("init_projects")) {
Vector<String> list = extra_config->get_value("init_projects", "list");
for (int i = 0; i < list.size(); i++) {
@@ -1080,9 +1026,6 @@ fail:
singleton = Ref<EditorSettings>(memnew(EditorSettings));
singleton->save_changed_setting = true;
singleton->config_file_path = config_file_path;
- singleton->settings_dir = config_dir;
- singleton->data_dir = data_dir;
- singleton->cache_dir = cache_dir;
singleton->_load_defaults(extra_config);
singleton->setup_language();
singleton->setup_network();
@@ -1312,30 +1255,22 @@ void EditorSettings::add_property_hint(const PropertyInfo &p_hint) {
// Data directories
-String EditorSettings::get_data_dir() const {
- return data_dir;
-}
-
String EditorSettings::get_templates_dir() const {
- return get_data_dir().plus_file("templates");
+ return EditorPaths::get_singleton()->get_data_dir().plus_file("templates");
}
// Config directories
-String EditorSettings::get_settings_dir() const {
- return settings_dir;
-}
-
String EditorSettings::get_project_settings_dir() const {
return EditorSettings::PROJECT_EDITOR_SETTINGS_PATH;
}
String EditorSettings::get_text_editor_themes_dir() const {
- return get_settings_dir().plus_file("text_editor_themes");
+ return EditorPaths::get_singleton()->get_settings_dir().plus_file("text_editor_themes");
}
String EditorSettings::get_script_templates_dir() const {
- return get_settings_dir().plus_file("script_templates");
+ return EditorPaths::get_singleton()->get_settings_dir().plus_file("script_templates");
}
String EditorSettings::get_project_script_templates_dir() const {
@@ -1344,12 +1279,8 @@ String EditorSettings::get_project_script_templates_dir() const {
// Cache directory
-String EditorSettings::get_cache_dir() const {
- return cache_dir;
-}
-
String EditorSettings::get_feature_profiles_dir() const {
- return get_settings_dir().plus_file("feature_profiles");
+ return EditorPaths::get_singleton()->get_settings_dir().plus_file("feature_profiles");
}
// Metadata
@@ -1576,7 +1507,7 @@ Vector<String> EditorSettings::get_script_templates(const String &p_extension, c
}
String EditorSettings::get_editor_layouts_config() const {
- return get_settings_dir().plus_file("editor_layouts.cfg");
+ return EditorPaths::get_singleton()->get_settings_dir().plus_file("editor_layouts.cfg");
}
// Shortcuts
@@ -1778,7 +1709,6 @@ void EditorSettings::_bind_methods() {
ClassDB::bind_method(D_METHOD("property_get_revert", "name"), &EditorSettings::property_get_revert);
ClassDB::bind_method(D_METHOD("add_property_info", "info"), &EditorSettings::_add_property_info_bind);
- ClassDB::bind_method(D_METHOD("get_settings_dir"), &EditorSettings::get_settings_dir);
ClassDB::bind_method(D_METHOD("get_project_settings_dir"), &EditorSettings::get_project_settings_dir);
ClassDB::bind_method(D_METHOD("set_project_metadata", "section", "key", "data"), &EditorSettings::set_project_metadata);
diff --git a/editor/editor_settings.h b/editor/editor_settings.h
index e5f8527faf..5c9eec34a2 100644
--- a/editor/editor_settings.h
+++ b/editor/editor_settings.h
@@ -36,6 +36,7 @@
#include "core/object/class_db.h"
#include "core/os/thread_safe.h"
#include "core/string/translation.h"
+#include "editor/editor_paths.h"
#include "scene/gui/shortcut.h"
class EditorPlugin;
@@ -87,12 +88,7 @@ private:
mutable Map<String, Ref<Shortcut>> shortcuts;
Map<String, List<Ref<InputEvent>>> builtin_action_overrides;
- String resource_path;
- String settings_dir;
- String data_dir;
- String cache_dir;
String config_file_path;
- String project_config_dir;
Vector<String> favorites;
Vector<String> recent_dirs;
@@ -153,12 +149,10 @@ public:
String get_data_dir() const;
String get_templates_dir() const;
- String get_settings_dir() const;
String get_project_settings_dir() const;
String get_text_editor_themes_dir() const;
String get_script_templates_dir() const;
String get_project_script_templates_dir() const;
- String get_cache_dir() const;
String get_feature_profiles_dir() const;
void set_project_metadata(const String &p_section, const String &p_key, Variant p_data);
diff --git a/editor/editor_themes.cpp b/editor/editor_themes.cpp
index 978714f719..3c703f3680 100644
--- a/editor/editor_themes.cpp
+++ b/editor/editor_themes.cpp
@@ -329,16 +329,18 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
preset_contrast = default_contrast;
} else if (preset == "Light") {
preset_accent_color = Color(0.18, 0.50, 1.00);
- preset_base_color = Color(1.00, 1.00, 1.00);
- preset_contrast = 0.08;
+ preset_base_color = Color(0.9, 0.9, 0.9);
+ // A negative contrast rate looks better for light themes, since it better follows the natural order of UI "elevation".
+ preset_contrast = -0.08;
} else if (preset == "Solarized (Dark)") {
preset_accent_color = Color(0.15, 0.55, 0.82);
preset_base_color = Color(0.04, 0.23, 0.27);
preset_contrast = default_contrast;
} else if (preset == "Solarized (Light)") {
preset_accent_color = Color(0.15, 0.55, 0.82);
- preset_base_color = Color(0.99, 0.96, 0.89);
- preset_contrast = 0.08;
+ preset_base_color = Color(0.89, 0.86, 0.79);
+ // A negative contrast rate looks better for light themes, since it better follows the natural order of UI "elevation".
+ preset_contrast = -0.08;
} else { // Default
preset_accent_color = Color(0.44, 0.73, 0.98);
preset_base_color = Color(0.21, 0.24, 0.29);
@@ -1355,7 +1357,8 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
const Color comment_color = dim_color;
const Color string_color = (dark_theme ? Color(1.0, 0.85, 0.26) : Color(1.0, 0.82, 0.09)).lerp(mono_color, dark_theme ? 0.5 : 0.3);
- const Color te_background_color = dark_theme ? background_color : base_color;
+ // Use the brightest background color on a light theme (which generally uses a negative contrast rate).
+ const Color te_background_color = dark_theme ? background_color : dark_color_3;
const Color completion_background_color = dark_theme ? base_color : background_color;
const Color completion_selected_color = alpha1;
const Color completion_existing_color = alpha2;
diff --git a/editor/export_template_manager.cpp b/editor/export_template_manager.cpp
index 0f5c01be0e..6e0ae403a2 100644
--- a/editor/export_template_manager.cpp
+++ b/editor/export_template_manager.cpp
@@ -444,7 +444,7 @@ void ExportTemplateManager::_begin_template_download(const String &p_url) {
}
download_data.clear();
- download_templates->set_download_file(EditorSettings::get_singleton()->get_cache_dir().plus_file("tmp_templates.tpz"));
+ download_templates->set_download_file(EditorPaths::get_singleton()->get_cache_dir().plus_file("tmp_templates.tpz"));
download_templates->set_use_threads(true);
Error err = download_templates->request(p_url);
diff --git a/editor/plugins/asset_library_editor_plugin.cpp b/editor/plugins/asset_library_editor_plugin.cpp
index a0d9afee74..93bb170128 100644
--- a/editor/plugins/asset_library_editor_plugin.cpp
+++ b/editor/plugins/asset_library_editor_plugin.cpp
@@ -464,7 +464,7 @@ void EditorAssetLibraryItemDownload::_make_request() {
retry->hide();
download->cancel_request();
- download->set_download_file(EditorSettings::get_singleton()->get_cache_dir().plus_file("tmp_asset_" + itos(asset_id)) + ".zip");
+ download->set_download_file(EditorPaths::get_singleton()->get_cache_dir().plus_file("tmp_asset_" + itos(asset_id)) + ".zip");
Error err = download->request(host);
if (err != OK) {
@@ -702,7 +702,7 @@ void EditorAssetLibrary::_image_update(bool use_cache, bool final, const PackedB
PackedByteArray image_data = p_data;
if (use_cache) {
- String cache_filename_base = EditorSettings::get_singleton()->get_cache_dir().plus_file("assetimage_" + image_queue[p_queue_id].image_url.md5_text());
+ String cache_filename_base = EditorPaths::get_singleton()->get_cache_dir().plus_file("assetimage_" + image_queue[p_queue_id].image_url.md5_text());
FileAccess *file = FileAccess::open(cache_filename_base + ".data", FileAccess::READ);
@@ -781,7 +781,7 @@ void EditorAssetLibrary::_image_request_completed(int p_status, int p_code, cons
if (p_code != HTTPClient::RESPONSE_NOT_MODIFIED) {
for (int i = 0; i < headers.size(); i++) {
if (headers[i].findn("ETag:") == 0) { // Save etag
- String cache_filename_base = EditorSettings::get_singleton()->get_cache_dir().plus_file("assetimage_" + image_queue[p_queue_id].image_url.md5_text());
+ String cache_filename_base = EditorPaths::get_singleton()->get_cache_dir().plus_file("assetimage_" + image_queue[p_queue_id].image_url.md5_text());
String new_etag = headers[i].substr(headers[i].find(":") + 1, headers[i].length()).strip_edges();
FileAccess *file;
@@ -829,7 +829,7 @@ void EditorAssetLibrary::_update_image_queue() {
List<int> to_delete;
for (Map<int, ImageQueue>::Element *E = image_queue.front(); E; E = E->next()) {
if (!E->get().active && current_images < max_images) {
- String cache_filename_base = EditorSettings::get_singleton()->get_cache_dir().plus_file("assetimage_" + E->get().image_url.md5_text());
+ String cache_filename_base = EditorPaths::get_singleton()->get_cache_dir().plus_file("assetimage_" + E->get().image_url.md5_text());
Vector<String> headers;
if (FileAccess::exists(cache_filename_base + ".etag") && FileAccess::exists(cache_filename_base + ".data")) {
diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp
index 5811e2b9ba..2be586733b 100644
--- a/editor/plugins/canvas_item_editor_plugin.cpp
+++ b/editor/plugins/canvas_item_editor_plugin.cpp
@@ -5361,8 +5361,10 @@ void CanvasItemEditor::_bind_methods() {
ClassDB::bind_method("_queue_update_bone_list", &CanvasItemEditor::_update_bone_list);
ClassDB::bind_method("_update_bone_list", &CanvasItemEditor::_update_bone_list);
ClassDB::bind_method("_reset_create_position", &CanvasItemEditor::_reset_create_position);
+ ClassDB::bind_method(D_METHOD("get_state"), &CanvasItemEditor::get_state);
ClassDB::bind_method(D_METHOD("set_state"), &CanvasItemEditor::set_state);
ClassDB::bind_method(D_METHOD("update_viewport"), &CanvasItemEditor::update_viewport);
+ ClassDB::bind_method(D_METHOD("_zoom_on_position"), &CanvasItemEditor::_zoom_on_position);
ADD_SIGNAL(MethodInfo("item_lock_status_changed"));
ADD_SIGNAL(MethodInfo("item_group_status_changed"));
diff --git a/editor/plugins/editor_preview_plugins.cpp b/editor/plugins/editor_preview_plugins.cpp
index 18cc5d43fb..2d79e4f3e3 100644
--- a/editor/plugins/editor_preview_plugins.cpp
+++ b/editor/plugins/editor_preview_plugins.cpp
@@ -265,7 +265,7 @@ Ref<Texture2D> EditorPackedScenePreviewPlugin::generate(const RES &p_from, const
}
Ref<Texture2D> EditorPackedScenePreviewPlugin::generate_from_path(const String &p_path, const Size2 &p_size) const {
- String temp_path = EditorSettings::get_singleton()->get_cache_dir();
+ String temp_path = EditorPaths::get_singleton()->get_cache_dir();
String cache_base = ProjectSettings::get_singleton()->globalize_path(p_path).md5_text();
cache_base = temp_path.plus_file("resthumb-" + cache_base);
diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp
index 72a4bd8243..fe5d830239 100644
--- a/editor/plugins/script_text_editor.cpp
+++ b/editor/plugins/script_text_editor.cpp
@@ -1429,11 +1429,17 @@ void ScriptTextEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data
Array files = d["files"];
String text_to_drop;
+ bool preload = Input::get_singleton()->is_key_pressed(KEY_CTRL);
for (int i = 0; i < files.size(); i++) {
if (i > 0) {
- text_to_drop += ",";
+ text_to_drop += ", ";
+ }
+
+ if (preload) {
+ text_to_drop += "preload(\"" + String(files[i]).c_escape() + "\")";
+ } else {
+ text_to_drop += "\"" + String(files[i]).c_escape() + "\"";
}
- text_to_drop += "\"" + String(files[i]).c_escape() + "\"";
}
te->cursor_set_line(row);
diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp
index 6fd61d2e00..a0757439c3 100644
--- a/editor/plugins/visual_shader_editor_plugin.cpp
+++ b/editor/plugins/visual_shader_editor_plugin.cpp
@@ -4260,6 +4260,7 @@ VisualShaderEditor::VisualShaderEditor() {
add_options.push_back(AddOption("TransformDecompose", "Transform", "Composition", "VisualShaderNodeTransformDecompose", TTR("Decomposes transform to four vectors.")));
add_options.push_back(AddOption("Determinant", "Transform", "Functions", "VisualShaderNodeDeterminant", TTR("Calculates the determinant of a transform."), -1, VisualShaderNode::PORT_TYPE_SCALAR));
+ add_options.push_back(AddOption("GetBillboardMatrix", "Transform", "Functions", "VisualShaderNodeBillboard", TTR("Calculates how the object should face the camera to be applied on Model View Matrix output port for 3D objects."), -1, VisualShaderNode::PORT_TYPE_TRANSFORM, TYPE_FLAGS_VERTEX, Shader::MODE_SPATIAL));
add_options.push_back(AddOption("Inverse", "Transform", "Functions", "VisualShaderNodeTransformFunc", TTR("Calculates the inverse of a transform."), VisualShaderNodeTransformFunc::FUNC_INVERSE, VisualShaderNode::PORT_TYPE_TRANSFORM));
add_options.push_back(AddOption("Transpose", "Transform", "Functions", "VisualShaderNodeTransformFunc", TTR("Calculates the transpose of a transform."), VisualShaderNodeTransformFunc::FUNC_TRANSPOSE, VisualShaderNode::PORT_TYPE_TRANSFORM));
diff --git a/main/main.cpp b/main/main.cpp
index 2f191b5f63..d67761db55 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -1445,6 +1445,12 @@ Error Main::setup2(Thread::ID p_main_tid_override) {
}
#endif
+#ifdef TOOLS_ENABLED
+ if (editor || project_manager) {
+ EditorNode::register_editor_paths(project_manager);
+ }
+#endif
+
/* Determine text driver */
if (text_driver == "") {
diff --git a/misc/dist/ios_xcode/godot_ios.xcodeproj/project.pbxproj b/misc/dist/ios_xcode/godot_ios.xcodeproj/project.pbxproj
index b9ad431e6e..fd69725a21 100644
--- a/misc/dist/ios_xcode/godot_ios.xcodeproj/project.pbxproj
+++ b/misc/dist/ios_xcode/godot_ios.xcodeproj/project.pbxproj
@@ -255,7 +255,7 @@
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
OTHER_LDFLAGS = "$linker_flags";
SDKROOT = iphoneos;
- TARGETED_DEVICE_FAMILY = "1,2";
+ TARGETED_DEVICE_FAMILY = "$targeted_device_family";
};
name = Debug;
};
@@ -294,7 +294,7 @@
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
OTHER_LDFLAGS = "$linker_flags";
SDKROOT = iphoneos;
- TARGETED_DEVICE_FAMILY = "1,2";
+ TARGETED_DEVICE_FAMILY = "$targeted_device_family";
VALIDATE_PRODUCT = YES;
};
name = Release;
@@ -323,7 +323,7 @@
PRODUCT_BUNDLE_IDENTIFIER = $bundle_identifier;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE = "$provisioning_profile_uuid_debug";
- TARGETED_DEVICE_FAMILY = "1,2";
+ TARGETED_DEVICE_FAMILY = "$targeted_device_family";
VALID_ARCHS = "armv7 armv7s arm64 i386 x86_64";
WRAPPER_EXTENSION = app;
};
@@ -353,7 +353,7 @@
PRODUCT_BUNDLE_IDENTIFIER = $bundle_identifier;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE = "$provisioning_profile_uuid_release";
- TARGETED_DEVICE_FAMILY = "1,2";
+ TARGETED_DEVICE_FAMILY = "$targeted_device_family";
VALID_ARCHS = "armv7 armv7s arm64 i386 x86_64";
WRAPPER_EXTENSION = app;
};
diff --git a/modules/glslang/register_types.cpp b/modules/glslang/register_types.cpp
index 4331daadfc..8979eabfc3 100644
--- a/modules/glslang/register_types.cpp
+++ b/modules/glslang/register_types.cpp
@@ -179,11 +179,18 @@ static Vector<uint8_t> _compile_shader_glsl(RenderingDevice::ShaderStage p_stage
return ret;
}
+static String _get_cache_key_function_glsl(const RenderingDevice::Capabilities *p_capabilities) {
+ String version;
+ version = "SpirVGen=" + itos(glslang::GetSpirvGeneratorVersion()) + ", major=" + itos(p_capabilities->version_major) + ", minor=" + itos(p_capabilities->version_minor) + " , subgroup_size=" + itos(p_capabilities->subgroup_operations) + " , subgroup_ops=" + itos(p_capabilities->subgroup_operations) + " , subgroup_in_shaders=" + itos(p_capabilities->subgroup_in_shaders);
+ return version;
+}
+
void preregister_glslang_types() {
// initialize in case it's not initialized. This is done once per thread
// and it's safe to call multiple times
glslang::InitializeProcess();
RenderingDevice::shader_set_compile_function(_compile_shader_glsl);
+ RenderingDevice::shader_set_get_cache_key_function(_get_cache_key_function_glsl);
}
void register_glslang_types() {
diff --git a/modules/gltf/doc_classes/GLTFNode.xml b/modules/gltf/doc_classes/GLTFNode.xml
index 5b7d4fadec..af7be55e4b 100644
--- a/modules/gltf/doc_classes/GLTFNode.xml
+++ b/modules/gltf/doc_classes/GLTFNode.xml
@@ -13,8 +13,6 @@
</member>
<member name="children" type="PackedInt32Array" setter="set_children" getter="get_children" default="PackedInt32Array( )">
</member>
- <member name="fake_joint_parent" type="int" setter="set_fake_joint_parent" getter="get_fake_joint_parent" default="-1">
- </member>
<member name="height" type="int" setter="set_height" getter="get_height" default="-1">
</member>
<member name="joint" type="bool" setter="set_joint" getter="get_joint" default="false">
diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp
index ac76506b87..abac0a2e17 100644
--- a/modules/gltf/gltf_document.cpp
+++ b/modules/gltf/gltf_document.cpp
@@ -2821,8 +2821,8 @@ Error GLTFDocument::_parse_meshes(Ref<GLTFState> state) {
}
blend_weights.write[j] = weights[j];
}
- mesh->set_blend_weights(blend_weights);
}
+ mesh->set_blend_weights(blend_weights);
mesh->set_mesh(import_mesh);
state->meshes.push_back(mesh);
@@ -4108,81 +4108,10 @@ Error GLTFDocument::_reparent_non_joint_skeleton_subtrees(Ref<GLTFState> state,
subtree_set.get_members(subtree_nodes, subtree_root);
for (int subtree_i = 0; subtree_i < subtree_nodes.size(); ++subtree_i) {
- ERR_FAIL_COND_V(_reparent_to_fake_joint(state, skeleton, subtree_nodes[subtree_i]), FAILED);
-
- // We modified the tree, recompute all the heights
- _compute_node_heights(state);
- }
- }
-
- return OK;
-}
-
-Error GLTFDocument::_reparent_to_fake_joint(Ref<GLTFState> state, Ref<GLTFSkeleton> skeleton, const GLTFNodeIndex node_index) {
- Ref<GLTFNode> node = state->nodes[node_index];
-
- // Can we just "steal" this joint if it is just a spatial node?
- if (node->skin < 0 && node->mesh < 0 && node->camera < 0) {
- node->joint = true;
- // Add the joint to the skeletons joints
- skeleton->joints.push_back(node_index);
- return OK;
- }
-
- GLTFNode *fake_joint = memnew(GLTFNode);
- const GLTFNodeIndex fake_joint_index = state->nodes.size();
- state->nodes.push_back(fake_joint);
-
- // We better not be a joint, or we messed up in our logic
- if (node->joint) {
- return FAILED;
- }
-
- fake_joint->translation = node->translation;
- fake_joint->rotation = node->rotation;
- fake_joint->scale = node->scale;
- fake_joint->xform = node->xform;
- fake_joint->joint = true;
-
- // We can use the exact same name here, because the joint will be inside a skeleton and not the scene
- fake_joint->set_name(node->get_name());
-
- // Clear the nodes transforms, since it will be parented to the fake joint
- node->translation = Vector3(0, 0, 0);
- node->rotation = Quat();
- node->scale = Vector3(1, 1, 1);
- node->xform = Transform();
-
- // Transfer the node children to the fake joint
- for (int child_i = 0; child_i < node->children.size(); ++child_i) {
- Ref<GLTFNode> child = state->nodes[node->children[child_i]];
- child->parent = fake_joint_index;
- }
-
- fake_joint->children = node->children;
- node->children.clear();
-
- // add the fake joint to the parent and remove the original joint
- if (node->parent >= 0) {
- Ref<GLTFNode> parent = state->nodes[node->parent];
- parent->children.erase(node_index);
- parent->children.push_back(fake_joint_index);
- fake_joint->parent = node->parent;
- }
-
- // Add the node to the fake joint
- fake_joint->children.push_back(node_index);
- node->parent = fake_joint_index;
- node->fake_joint_parent = fake_joint_index;
-
- // Add the fake joint to the skeletons joints
- skeleton->joints.push_back(fake_joint_index);
-
- // Replace skin_skeletons with fake joints if we must.
- for (GLTFSkinIndex skin_i = 0; skin_i < state->skins.size(); ++skin_i) {
- Ref<GLTFSkin> skin = state->skins.write[skin_i];
- if (skin->skin_root == node_index) {
- skin->skin_root = fake_joint_index;
+ Ref<GLTFNode> node = state->nodes[subtree_nodes[subtree_i]];
+ node->joint = true;
+ // Add the joint to the skeletons joints
+ skeleton->joints.push_back(subtree_nodes[subtree_i]);
}
}
@@ -4920,10 +4849,9 @@ void GLTFDocument::_assign_scene_names(Ref<GLTFState> state) {
}
}
-BoneAttachment3D *GLTFDocument::_generate_bone_attachment(Ref<GLTFState> state, Skeleton3D *skeleton, const GLTFNodeIndex node_index) {
+BoneAttachment3D *GLTFDocument::_generate_bone_attachment(Ref<GLTFState> state, Skeleton3D *skeleton, const GLTFNodeIndex node_index, const GLTFNodeIndex bone_index) {
Ref<GLTFNode> gltf_node = state->nodes[node_index];
- Ref<GLTFNode> bone_node = state->nodes[gltf_node->parent];
-
+ Ref<GLTFNode> bone_node = state->nodes[bone_index];
BoneAttachment3D *bone_attachment = memnew(BoneAttachment3D);
print_verbose("glTF: Creating bone attachment for: " + gltf_node->get_name());
@@ -5008,7 +4936,7 @@ EditorSceneImporterMeshNode3D *GLTFDocument::_generate_mesh_instance(Ref<GLTFSta
return mi;
}
-Light3D *GLTFDocument::_generate_light(Ref<GLTFState> state, Node *scene_parent, const GLTFNodeIndex node_index) {
+Node3D *GLTFDocument::_generate_light(Ref<GLTFState> state, Node *scene_parent, const GLTFNodeIndex node_index) {
Ref<GLTFNode> gltf_node = state->nodes[node_index];
ERR_FAIL_INDEX_V(gltf_node->light, state->lights.size(), nullptr);
@@ -5057,7 +4985,7 @@ Light3D *GLTFDocument::_generate_light(Ref<GLTFState> state, Node *scene_parent,
light->set_param(SpotLight3D::PARAM_SPOT_ATTENUATION, angle_attenuation);
return light;
}
- return nullptr;
+ return memnew(Node3D);
}
Camera3D *GLTFDocument::_generate_camera(Ref<GLTFState> state, Node *scene_parent, const GLTFNodeIndex node_index) {
@@ -5429,31 +5357,22 @@ void GLTFDocument::_convert_mesh_to_gltf(Node *p_scene_parent, Ref<GLTFState> st
void GLTFDocument::_generate_scene_node(Ref<GLTFState> state, Node *scene_parent, Node3D *scene_root, const GLTFNodeIndex node_index) {
Ref<GLTFNode> gltf_node = state->nodes[node_index];
+ if (gltf_node->skeleton >= 0) {
+ _generate_skeleton_bone_node(state, scene_parent, scene_root, node_index);
+ return;
+ }
+
Node3D *current_node = nullptr;
// Is our parent a skeleton
Skeleton3D *active_skeleton = Object::cast_to<Skeleton3D>(scene_parent);
- if (gltf_node->skeleton >= 0) {
- Skeleton3D *skeleton = state->skeletons[gltf_node->skeleton]->godot_skeleton;
-
- if (active_skeleton != skeleton) {
- ERR_FAIL_COND_MSG(active_skeleton != nullptr, "glTF: Generating scene detected direct parented Skeletons");
-
- // Add it to the scene if it has not already been added
- if (skeleton->get_parent() == nullptr) {
- scene_parent->add_child(skeleton);
- skeleton->set_owner(scene_root);
- }
- }
-
- active_skeleton = skeleton;
- current_node = skeleton;
- }
+ const bool non_bone_parented_to_skeleton = active_skeleton;
- // If we have an active skeleton, and the node is node skinned, we need to create a bone attachment
- if (current_node == nullptr && active_skeleton != nullptr && gltf_node->skin < 0) {
- BoneAttachment3D *bone_attachment = _generate_bone_attachment(state, active_skeleton, node_index);
+ // skinned meshes must not be placed in a bone attachment.
+ if (non_bone_parented_to_skeleton && gltf_node->skin < 0) {
+ // Bone Attachment - Parent Case
+ BoneAttachment3D *bone_attachment = _generate_bone_attachment(state, active_skeleton, node_index, gltf_node->parent);
scene_parent->add_child(bone_attachment);
bone_attachment->set_owner(scene_root);
@@ -5467,7 +5386,86 @@ void GLTFDocument::_generate_scene_node(Ref<GLTFState> state, Node *scene_parent
}
// We still have not managed to make a node
- if (current_node == nullptr) {
+ if (gltf_node->mesh >= 0) {
+ current_node = _generate_mesh_instance(state, scene_parent, node_index);
+ } else if (gltf_node->camera >= 0) {
+ current_node = _generate_camera(state, scene_parent, node_index);
+ } else if (gltf_node->light >= 0) {
+ current_node = _generate_light(state, scene_parent, node_index);
+ } else {
+ current_node = _generate_spatial(state, scene_parent, node_index);
+ }
+
+ scene_parent->add_child(current_node);
+ if (current_node != scene_root) {
+ current_node->set_owner(scene_root);
+ }
+ current_node->set_transform(gltf_node->xform);
+ current_node->set_name(gltf_node->get_name());
+
+ state->scene_nodes.insert(node_index, current_node);
+
+ for (int i = 0; i < gltf_node->children.size(); ++i) {
+ _generate_scene_node(state, current_node, scene_root, gltf_node->children[i]);
+ }
+}
+
+void GLTFDocument::_generate_skeleton_bone_node(Ref<GLTFState> state, Node *scene_parent, Node3D *scene_root, const GLTFNodeIndex node_index) {
+ Ref<GLTFNode> gltf_node = state->nodes[node_index];
+
+ Node3D *current_node = nullptr;
+
+ Skeleton3D *skeleton = state->skeletons[gltf_node->skeleton]->godot_skeleton;
+ // In this case, this node is already a bone in skeleton.
+ const bool is_skinned_mesh = (gltf_node->skin >= 0 && gltf_node->mesh >= 0);
+ const bool requires_extra_node = (gltf_node->mesh >= 0 || gltf_node->camera >= 0 || gltf_node->light >= 0);
+
+ Skeleton3D *active_skeleton = Object::cast_to<Skeleton3D>(scene_parent);
+ if (active_skeleton != skeleton) {
+ if (active_skeleton) {
+ // Bone Attachment - Direct Parented Skeleton Case
+ BoneAttachment3D *bone_attachment = _generate_bone_attachment(state, active_skeleton, node_index, gltf_node->parent);
+
+ scene_parent->add_child(bone_attachment);
+ bone_attachment->set_owner(scene_root);
+
+ // There is no gltf_node that represent this, so just directly create a unique name
+ bone_attachment->set_name(_gen_unique_name(state, "BoneAttachment3D"));
+
+ // We change the scene_parent to our bone attachment now. We do not set current_node because we want to make the node
+ // and attach it to the bone_attachment
+ scene_parent = bone_attachment;
+ WARN_PRINT(vformat("glTF: Generating scene detected direct parented Skeletons at node %d", node_index));
+ }
+
+ // Add it to the scene if it has not already been added
+ if (skeleton->get_parent() == nullptr) {
+ scene_parent->add_child(skeleton);
+ skeleton->set_owner(scene_root);
+ }
+ }
+
+ active_skeleton = skeleton;
+ current_node = skeleton;
+
+ if (requires_extra_node) {
+ // skinned meshes must not be placed in a bone attachment.
+ if (!is_skinned_mesh) {
+ // Bone Attachment - Same Node Case
+ BoneAttachment3D *bone_attachment = _generate_bone_attachment(state, active_skeleton, node_index, node_index);
+
+ scene_parent->add_child(bone_attachment);
+ bone_attachment->set_owner(scene_root);
+
+ // There is no gltf_node that represent this, so just directly create a unique name
+ bone_attachment->set_name(_gen_unique_name(state, "BoneAttachment3D"));
+
+ // We change the scene_parent to our bone attachment now. We do not set current_node because we want to make the node
+ // and attach it to the bone_attachment
+ scene_parent = bone_attachment;
+ }
+
+ // We still have not managed to make a node
if (gltf_node->mesh >= 0) {
current_node = _generate_mesh_instance(state, scene_parent, node_index);
} else if (gltf_node->camera >= 0) {
@@ -5476,22 +5474,18 @@ void GLTFDocument::_generate_scene_node(Ref<GLTFState> state, Node *scene_parent
current_node = _generate_light(state, scene_parent, node_index);
}
- if (!current_node) {
- current_node = _generate_spatial(state, scene_parent, node_index);
- }
-
scene_parent->add_child(current_node);
if (current_node != scene_root) {
current_node->set_owner(scene_root);
}
- current_node->set_transform(gltf_node->xform);
+ // Do not set transform here. Transform is already applied to our bone.
current_node->set_name(gltf_node->get_name());
}
state->scene_nodes.insert(node_index, current_node);
for (int i = 0; i < gltf_node->children.size(); ++i) {
- _generate_scene_node(state, current_node, scene_root, gltf_node->children[i]);
+ _generate_scene_node(state, active_skeleton, scene_root, gltf_node->children[i]);
}
}
@@ -5632,28 +5626,30 @@ void GLTFDocument::_import_animation(Ref<GLTFState> state, AnimationPlayer *ap,
for (Map<int, GLTFAnimation::Track>::Element *track_i = anim->get_tracks().front(); track_i; track_i = track_i->next()) {
const GLTFAnimation::Track &track = track_i->get();
- //need to find the path
+ //need to find the path: for skeletons, weight tracks will affect the mesh
NodePath node_path;
+ //for skeletons, transform tracks always affect bones
+ NodePath transform_node_path;
GLTFNodeIndex node_index = track_i->key();
- if (state->nodes[node_index]->fake_joint_parent >= 0) {
- // Should be same as parent
- node_index = state->nodes[node_index]->fake_joint_parent;
- }
const Ref<GLTFNode> gltf_node = state->nodes[track_i->key()];
+ Node *root = ap->get_parent();
+ ERR_FAIL_COND(root == nullptr);
+ Map<GLTFNodeIndex, Node *>::Element *node_element = state->scene_nodes.find(node_index);
+ ERR_CONTINUE_MSG(node_element == nullptr, vformat("Unable to find node %d for animation", node_index));
+ node_path = root->get_path_to(node_element->get());
+
if (gltf_node->skeleton >= 0) {
- const Skeleton3D *sk = Object::cast_to<Skeleton3D>(state->scene_nodes.find(node_index)->get());
+ const Skeleton3D *sk = state->skeletons[gltf_node->skeleton]->godot_skeleton;
ERR_FAIL_COND(sk == nullptr);
const String path = ap->get_parent()->get_path_to(sk);
const String bone = gltf_node->get_name();
- node_path = path + ":" + bone;
+ transform_node_path = path + ":" + bone;
} else {
- Node *root = ap->get_parent();
- Node *godot_node = state->scene_nodes.find(node_index)->get();
- node_path = root->get_path_to(godot_node);
+ transform_node_path = node_path;
}
for (int i = 0; i < track.rotation_track.times.size(); i++) {
@@ -5672,11 +5668,13 @@ void GLTFDocument::_import_animation(Ref<GLTFState> state, AnimationPlayer *ap,
}
}
- if (track.rotation_track.values.size() || track.translation_track.values.size() || track.scale_track.values.size()) {
+ // Animated TRS properties will not affect a skinned mesh.
+ const bool transform_affects_skinned_mesh_instance = gltf_node->skeleton < 0 && gltf_node->skin >= 0;
+ if ((track.rotation_track.values.size() || track.translation_track.values.size() || track.scale_track.values.size()) && !transform_affects_skinned_mesh_instance) {
//make transform track
int track_idx = animation->get_track_count();
animation->add_track(Animation::TYPE_TRANSFORM);
- animation->track_set_path(track_idx, node_path);
+ animation->track_set_path(track_idx, transform_node_path);
//first determine animation length
const double increment = 1.0 / bake_fps;
diff --git a/modules/gltf/gltf_document.h b/modules/gltf/gltf_document.h
index bda1ce87d6..900c367010 100644
--- a/modules/gltf/gltf_document.h
+++ b/modules/gltf/gltf_document.h
@@ -260,11 +260,12 @@ private:
Error _serialize_animations(Ref<GLTFState> state);
BoneAttachment3D *_generate_bone_attachment(Ref<GLTFState> state,
Skeleton3D *skeleton,
- const GLTFNodeIndex node_index);
+ const GLTFNodeIndex node_index,
+ const GLTFNodeIndex bone_index);
EditorSceneImporterMeshNode3D *_generate_mesh_instance(Ref<GLTFState> state, Node *scene_parent, const GLTFNodeIndex node_index);
Camera3D *_generate_camera(Ref<GLTFState> state, Node *scene_parent,
const GLTFNodeIndex node_index);
- Light3D *_generate_light(Ref<GLTFState> state, Node *scene_parent, const GLTFNodeIndex node_index);
+ Node3D *_generate_light(Ref<GLTFState> state, Node *scene_parent, const GLTFNodeIndex node_index);
Node3D *_generate_spatial(Ref<GLTFState> state, Node *scene_parent,
const GLTFNodeIndex node_index);
void _assign_scene_names(Ref<GLTFState> state);
@@ -365,6 +366,7 @@ public:
void _generate_scene_node(Ref<GLTFState> state, Node *scene_parent,
Node3D *scene_root,
const GLTFNodeIndex node_index);
+ void _generate_skeleton_bone_node(Ref<GLTFState> state, Node *scene_parent, Node3D *scene_root, const GLTFNodeIndex node_index);
void _import_animation(Ref<GLTFState> state, AnimationPlayer *ap,
const GLTFAnimationIndex index, const int bake_fps);
GLTFMeshIndex _convert_mesh_instance(Ref<GLTFState> state,
diff --git a/modules/gltf/gltf_node.cpp b/modules/gltf/gltf_node.cpp
index 777c6fbd9a..f6f33ef009 100644
--- a/modules/gltf/gltf_node.cpp
+++ b/modules/gltf/gltf_node.cpp
@@ -55,8 +55,6 @@ void GLTFNode::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_scale", "scale"), &GLTFNode::set_scale);
ClassDB::bind_method(D_METHOD("get_children"), &GLTFNode::get_children);
ClassDB::bind_method(D_METHOD("set_children", "children"), &GLTFNode::set_children);
- ClassDB::bind_method(D_METHOD("get_fake_joint_parent"), &GLTFNode::get_fake_joint_parent);
- ClassDB::bind_method(D_METHOD("set_fake_joint_parent", "fake_joint_parent"), &GLTFNode::set_fake_joint_parent);
ClassDB::bind_method(D_METHOD("get_light"), &GLTFNode::get_light);
ClassDB::bind_method(D_METHOD("set_light", "light"), &GLTFNode::set_light);
@@ -72,7 +70,6 @@ void GLTFNode::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::QUAT, "rotation"), "set_rotation", "get_rotation"); // Quat
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "scale"), "set_scale", "get_scale"); // Vector3
ADD_PROPERTY(PropertyInfo(Variant::PACKED_INT32_ARRAY, "children"), "set_children", "get_children"); // Vector<int>
- ADD_PROPERTY(PropertyInfo(Variant::INT, "fake_joint_parent"), "set_fake_joint_parent", "get_fake_joint_parent"); // GLTFNodeIndex
ADD_PROPERTY(PropertyInfo(Variant::INT, "light"), "set_light", "get_light"); // GLTFLightIndex
}
@@ -172,14 +169,6 @@ void GLTFNode::set_children(Vector<int> p_children) {
children = p_children;
}
-GLTFNodeIndex GLTFNode::get_fake_joint_parent() {
- return fake_joint_parent;
-}
-
-void GLTFNode::set_fake_joint_parent(GLTFNodeIndex p_fake_joint_parent) {
- fake_joint_parent = p_fake_joint_parent;
-}
-
GLTFLightIndex GLTFNode::get_light() {
return light;
}
diff --git a/modules/gltf/gltf_node.h b/modules/gltf/gltf_node.h
index ce8aff8944..3a5689d004 100644
--- a/modules/gltf/gltf_node.h
+++ b/modules/gltf/gltf_node.h
@@ -53,7 +53,6 @@ private:
Quat rotation;
Vector3 scale = Vector3(1, 1, 1);
Vector<int> children;
- GLTFNodeIndex fake_joint_parent = -1;
GLTFLightIndex light = -1;
protected:
@@ -96,9 +95,6 @@ public:
Vector<int> get_children();
void set_children(Vector<int> p_children);
- GLTFNodeIndex get_fake_joint_parent();
- void set_fake_joint_parent(GLTFNodeIndex p_fake_joint_parent);
-
GLTFLightIndex get_light();
void set_light(GLTFLightIndex p_light);
};
diff --git a/modules/mono/godotsharp_dirs.cpp b/modules/mono/godotsharp_dirs.cpp
index 020a40575c..68134b9b20 100644
--- a/modules/mono/godotsharp_dirs.cpp
+++ b/modules/mono/godotsharp_dirs.cpp
@@ -63,8 +63,8 @@ String _get_expected_build_config() {
String _get_mono_user_dir() {
#ifdef TOOLS_ENABLED
- if (EditorSettings::get_singleton()) {
- return EditorSettings::get_singleton()->get_data_dir().plus_file("mono");
+ if (EditorPaths::get_singleton()) {
+ return EditorPaths::get_singleton()->get_data_dir().plus_file("mono");
} else {
String settings_path;
diff --git a/platform/android/export/export.cpp b/platform/android/export/export.cpp
index 073c9dc6ef..1338b31a64 100644
--- a/platform/android/export/export.cpp
+++ b/platform/android/export/export.cpp
@@ -1793,7 +1793,7 @@ public:
p_debug_flags |= DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST;
}
- String tmp_export_path = EditorSettings::get_singleton()->get_cache_dir().plus_file("tmpexport." + uitos(OS::get_singleton()->get_unix_time()) + ".apk");
+ String tmp_export_path = EditorPaths::get_singleton()->get_cache_dir().plus_file("tmpexport." + uitos(OS::get_singleton()->get_unix_time()) + ".apk");
#define CLEANUP_AND_RETURN(m_err) \
{ \
@@ -2651,7 +2651,7 @@ public:
FileAccess *dst_f = nullptr;
io2.opaque = &dst_f;
- String tmp_unaligned_path = EditorSettings::get_singleton()->get_cache_dir().plus_file("tmpexport-unaligned." + uitos(OS::get_singleton()->get_unix_time()) + ".apk");
+ String tmp_unaligned_path = EditorPaths::get_singleton()->get_cache_dir().plus_file("tmpexport-unaligned." + uitos(OS::get_singleton()->get_unix_time()) + ".apk");
#define CLEANUP_AND_RETURN(m_err) \
{ \
diff --git a/platform/iphone/export/export.cpp b/platform/iphone/export/export.cpp
index 08ad77e940..73723b98a0 100644
--- a/platform/iphone/export/export.cpp
+++ b/platform/iphone/export/export.cpp
@@ -353,6 +353,8 @@ void EditorExportPlatformIOS::get_export_options(List<ExportOption> *r_options)
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/code_sign_identity_release", PROPERTY_HINT_PLACEHOLDER_TEXT, "iPhone Distribution"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/export_method_release", PROPERTY_HINT_ENUM, "App Store,Development,Ad-Hoc,Enterprise"), 0));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/targeted_device_family", PROPERTY_HINT_ENUM, "iPhone,iPad,iPhone & iPad"), 2));
+
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Game Name"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/info"), "Made with Godot Engine"));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/bundle_identifier", PROPERTY_HINT_PLACEHOLDER_TEXT, "com.example.game"), ""));
@@ -470,6 +472,20 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref<EditorExportPreset> &p_
strnew += lines[i].replace("$godot_archs", p_config.architectures) + "\n";
} else if (lines[i].find("$linker_flags") != -1) {
strnew += lines[i].replace("$linker_flags", p_config.linker_flags) + "\n";
+ } else if (lines[i].find("$targeted_device_family") != -1) {
+ String xcode_value;
+ switch ((int)p_preset->get("application/targeted_device_family")) {
+ case 0: // iPhone
+ xcode_value = "1";
+ break;
+ case 1: // iPad
+ xcode_value = "2";
+ break;
+ case 2: // iPhone & iPad
+ xcode_value = "1,2";
+ break;
+ }
+ strnew += lines[i].replace("$targeted_device_family", xcode_value) + "\n";
} else if (lines[i].find("$cpp_code") != -1) {
strnew += lines[i].replace("$cpp_code", p_config.cpp_code) + "\n";
} else if (lines[i].find("$docs_in_place") != -1) {
diff --git a/platform/javascript/export/export.cpp b/platform/javascript/export/export.cpp
index 5096285e33..8ce294f31b 100644
--- a/platform/javascript/export/export.cpp
+++ b/platform/javascript/export/export.cpp
@@ -63,7 +63,7 @@ private:
}
void _set_internal_certs(Ref<Crypto> p_crypto) {
- const String cache_path = EditorSettings::get_singleton()->get_cache_dir();
+ const String cache_path = EditorPaths::get_singleton()->get_cache_dir();
const String key_path = cache_path.plus_file("html5_server.key");
const String crt_path = cache_path.plus_file("html5_server.crt");
bool regen = !FileAccess::exists(key_path) || !FileAccess::exists(crt_path);
@@ -138,7 +138,7 @@ public:
const String req_file = req[1].get_file();
const String req_ext = req[1].get_extension();
- const String cache_path = EditorSettings::get_singleton()->get_cache_dir().plus_file("web");
+ const String cache_path = EditorPaths::get_singleton()->get_cache_dir().plus_file("web");
const String filepath = cache_path.plus_file(req_file);
if (!mimes.has(req_ext) || !FileAccess::exists(filepath)) {
@@ -888,7 +888,7 @@ Error EditorExportPlatformJavaScript::run(const Ref<EditorExportPreset> &p_prese
return OK;
}
- const String dest = EditorSettings::get_singleton()->get_cache_dir().plus_file("web");
+ const String dest = EditorPaths::get_singleton()->get_cache_dir().plus_file("web");
DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
if (!da->dir_exists(dest)) {
Error err = da->make_dir_recursive(dest);
diff --git a/platform/osx/export/export.cpp b/platform/osx/export/export.cpp
index 3a6a5333dd..6d995412ab 100644
--- a/platform/osx/export/export.cpp
+++ b/platform/osx/export/export.cpp
@@ -301,7 +301,7 @@ void EditorExportPlatformOSX::_make_icon(const Ref<Image> &p_icon, Vector<uint8_
if (icon_infos[i].is_png) {
// Encode PNG icon.
it->create_from_image(copy);
- String path = EditorSettings::get_singleton()->get_cache_dir().plus_file("icon.png");
+ String path = EditorPaths::get_singleton()->get_cache_dir().plus_file("icon.png");
ResourceSaver::save(path, it);
FileAccess *f = FileAccess::open(path, FileAccess::READ);
@@ -610,7 +610,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
// Create our application bundle.
String tmp_app_dir_name = pkg_name + ".app";
- String tmp_app_path_name = EditorSettings::get_singleton()->get_cache_dir().plus_file(tmp_app_dir_name);
+ String tmp_app_path_name = EditorPaths::get_singleton()->get_cache_dir().plus_file(tmp_app_dir_name);
print_line("Exporting to " + tmp_app_path_name);
Error err = OK;
@@ -774,7 +774,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
String ent_path = p_preset->get("codesign/entitlements/custom_file");
if (sign_enabled && (ent_path == "")) {
- ent_path = EditorSettings::get_singleton()->get_cache_dir().plus_file(pkg_name + ".entitlements");
+ ent_path = EditorPaths::get_singleton()->get_cache_dir().plus_file(pkg_name + ".entitlements");
FileAccess *ent_f = FileAccess::open(ent_path, FileAccess::WRITE);
if (ent_f) {
@@ -959,7 +959,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
zlib_filefunc_def io_dst = zipio_create_io_from_file(&dst_f);
zipFile zip = zipOpen2(p_path.utf8().get_data(), APPEND_STATUS_CREATE, nullptr, &io_dst);
- _zip_folder_recursive(zip, EditorSettings::get_singleton()->get_cache_dir(), pkg_name + ".app", pkg_name);
+ _zip_folder_recursive(zip, EditorPaths::get_singleton()->get_cache_dir(), pkg_name + ".app", pkg_name);
zipClose(zip, nullptr);
}
diff --git a/platform/uwp/export/export.cpp b/platform/uwp/export/export.cpp
index f1a857d414..351aaa5957 100644
--- a/platform/uwp/export/export.cpp
+++ b/platform/uwp/export/export.cpp
@@ -567,7 +567,7 @@ void AppxPackager::finish() {
// Create and add block map file
EditorNode::progress_task_step("export", "Creating block map...", 4);
- const String &tmp_blockmap_file_path = EditorSettings::get_singleton()->get_cache_dir().plus_file("tmpblockmap.xml");
+ const String &tmp_blockmap_file_path = EditorPaths::get_singleton()->get_cache_dir().plus_file("tmpblockmap.xml");
make_block_map(tmp_blockmap_file_path);
FileAccess *blockmap_file = FileAccess::open(tmp_blockmap_file_path, FileAccess::READ);
@@ -585,7 +585,7 @@ void AppxPackager::finish() {
EditorNode::progress_task_step("export", "Setting content types...", 5);
- const String &tmp_content_types_file_path = EditorSettings::get_singleton()->get_cache_dir().plus_file("tmpcontenttypes.xml");
+ const String &tmp_content_types_file_path = EditorPaths::get_singleton()->get_cache_dir().plus_file("tmpcontenttypes.xml");
make_content_types(tmp_content_types_file_path);
FileAccess *types_file = FileAccess::open(tmp_content_types_file_path, FileAccess::READ);
@@ -879,7 +879,7 @@ class EditorExportPlatformUWP : public EditorExportPlatform {
return data;
}
- String tmp_path = EditorSettings::get_singleton()->get_cache_dir().plus_file("uwp_tmp_logo.png");
+ String tmp_path = EditorPaths::get_singleton()->get_cache_dir().plus_file("uwp_tmp_logo.png");
Error err = texture->get_image()->save_png(tmp_path);
diff --git a/scene/2d/camera_2d.cpp b/scene/2d/camera_2d.cpp
index 01045502d5..ca4b8d72a1 100644
--- a/scene/2d/camera_2d.cpp
+++ b/scene/2d/camera_2d.cpp
@@ -270,11 +270,10 @@ void Camera2D::_notification(int p_what) {
}
if (screen_drawing_enabled) {
- Color area_axis_color(0.5, 0.42, 0.87, 0.63);
+ Color area_axis_color(1, 0.4, 1, 0.63);
real_t area_axis_width = 1;
if (is_current()) {
area_axis_width = 3;
- area_axis_color.a = 0.83;
}
Transform2D inv_camera_transform = get_camera_transform().affine_inverse();
@@ -295,10 +294,9 @@ void Camera2D::_notification(int p_what) {
}
if (limit_drawing_enabled) {
- Color limit_drawing_color(1, 1, 0, 0.63);
+ Color limit_drawing_color(1, 1, 0.25, 0.63);
real_t limit_drawing_width = 1;
if (is_current()) {
- limit_drawing_color.a = 0.83;
limit_drawing_width = 3;
}
@@ -317,11 +315,10 @@ void Camera2D::_notification(int p_what) {
}
if (margin_drawing_enabled) {
- Color margin_drawing_color(0, 1, 1, 0.63);
+ Color margin_drawing_color(0.25, 1, 1, 0.63);
real_t margin_drawing_width = 1;
if (is_current()) {
margin_drawing_width = 3;
- margin_drawing_color.a = 0.83;
}
Transform2D inv_camera_transform = get_camera_transform().affine_inverse();
diff --git a/scene/3d/skeleton_3d.cpp b/scene/3d/skeleton_3d.cpp
index 93f7dbcd91..82927df5f1 100644
--- a/scene/3d/skeleton_3d.cpp
+++ b/scene/3d/skeleton_3d.cpp
@@ -698,7 +698,7 @@ void Skeleton3D::_rebuild_physical_bones_cache() {
const int b_size = bones.size();
for (int i = 0; i < b_size; ++i) {
PhysicalBone3D *parent_pb = _get_physical_bone_parent(i);
- if (parent_pb != bones[i].physical_bone) {
+ if (parent_pb != bones[i].cache_parent_physical_bone) {
bones.write[i].cache_parent_physical_bone = parent_pb;
if (bones[i].physical_bone) {
bones[i].physical_bone->_on_bone_parent_changed();
diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp
index b9c1659fd1..e8ee4cb9fc 100644
--- a/scene/register_scene_types.cpp
+++ b/scene/register_scene_types.cpp
@@ -597,6 +597,7 @@ void register_scene_types() {
ClassDB::register_class<VisualShaderNodeIs>();
ClassDB::register_class<VisualShaderNodeCompare>();
ClassDB::register_class<VisualShaderNodeMultiplyAdd>();
+ ClassDB::register_class<VisualShaderNodeBillboard>();
ClassDB::register_class<VisualShaderNodeSDFToScreenUV>();
ClassDB::register_class<VisualShaderNodeScreenUVToSDF>();
diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp
index 5ac1ad935e..f37b1a3e8e 100644
--- a/scene/resources/visual_shader.cpp
+++ b/scene/resources/visual_shader.cpp
@@ -152,6 +152,14 @@ bool VisualShaderNode::is_use_prop_slots() const {
return false;
}
+bool VisualShaderNode::is_disabled() const {
+ return disabled;
+}
+
+void VisualShaderNode::set_disabled(bool p_disabled) {
+ disabled = p_disabled;
+}
+
Vector<VisualShader::DefaultTextureParam> VisualShaderNode::get_default_texture_parameters(VisualShader::Type p_type, int p_id) const {
return Vector<VisualShader::DefaultTextureParam>();
}
@@ -1260,6 +1268,12 @@ void VisualShader::_get_property_list(List<PropertyInfo> *p_list) const {
Error VisualShader::_write_node(Type type, StringBuilder &global_code, StringBuilder &global_code_per_node, Map<Type, StringBuilder> &global_code_per_func, StringBuilder &code, Vector<VisualShader::DefaultTextureParam> &def_tex_params, const VMap<ConnectionKey, const List<Connection>::Element *> &input_connections, const VMap<ConnectionKey, const List<Connection>::Element *> &output_connections, int node, Set<int> &processed, bool for_preview, Set<StringName> &r_classes) const {
const Ref<VisualShaderNode> vsnode = graph[type].nodes[node].node;
+ if (vsnode->is_disabled()) {
+ code += "// " + vsnode->get_caption() + ":" + itos(node) + "\n";
+ code += "\t// Node is disabled and code is not generated.\n";
+ return OK;
+ }
+
//check inputs recursively first
int input_count = vsnode->get_input_port_count();
for (int i = 0; i < input_count; i++) {
@@ -1328,6 +1342,11 @@ Error VisualShader::_write_node(Type type, StringBuilder &global_code, StringBui
if (input_connections.has(ck)) {
//connected to something, use that output
int from_node = input_connections[ck]->get().from_node;
+
+ if (graph[type].nodes[from_node].node->is_disabled()) {
+ continue;
+ }
+
int from_port = input_connections[ck]->get().from_port;
VisualShaderNode::PortType in_type = vsnode->get_input_port_type(i);
@@ -2531,6 +2550,8 @@ const VisualShaderNodeOutput::Port VisualShaderNodeOutput::ports[] = {
{ Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_VECTOR, "color", "COLOR.rgb" },
{ Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "alpha", "COLOR.a" },
{ Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_SCALAR, "roughness", "ROUGHNESS" },
+ { Shader::MODE_SPATIAL, VisualShader::TYPE_VERTEX, VisualShaderNode::PORT_TYPE_TRANSFORM, "model_view_matrix", "MODELVIEW_MATRIX" },
+
// Spatial, Fragment
{ Shader::MODE_SPATIAL, VisualShader::TYPE_FRAGMENT, VisualShaderNode::PORT_TYPE_VECTOR, "albedo", "ALBEDO" },
@@ -2670,9 +2691,13 @@ String VisualShaderNodeOutput::get_output_port_name(int p_port) const {
}
bool VisualShaderNodeOutput::is_port_separator(int p_index) const {
+ if (shader_mode == Shader::MODE_SPATIAL && shader_type == VisualShader::TYPE_VERTEX) {
+ String name = get_input_port_name(p_index);
+ return bool(name == "Model View Matrix");
+ }
if (shader_mode == Shader::MODE_SPATIAL && shader_type == VisualShader::TYPE_FRAGMENT) {
String name = get_input_port_name(p_index);
- return (name == "Normal" || name == "Rim" || name == "Alpha Scissor Threshold");
+ return bool(name == "Normal" || name == "Rim" || name == "Alpha Scissor Threshold");
}
return false;
}
diff --git a/scene/resources/visual_shader.h b/scene/resources/visual_shader.h
index cc9a867e64..aa7768751e 100644
--- a/scene/resources/visual_shader.h
+++ b/scene/resources/visual_shader.h
@@ -203,6 +203,8 @@ class VisualShaderNode : public Resource {
protected:
bool simple_decl = true;
+ bool disabled = false;
+
static void _bind_methods();
public:
@@ -257,6 +259,9 @@ public:
virtual bool is_show_prop_names() const;
virtual bool is_use_prop_slots() const;
+ bool is_disabled() const;
+ void set_disabled(bool p_disabled = true);
+
virtual Vector<StringName> get_editable_properties() const;
virtual Vector<VisualShader::DefaultTextureParam> get_default_texture_parameters(VisualShader::Type p_type, int p_id) const;
diff --git a/scene/resources/visual_shader_nodes.cpp b/scene/resources/visual_shader_nodes.cpp
index 314333158c..e7cc78cb3a 100644
--- a/scene/resources/visual_shader_nodes.cpp
+++ b/scene/resources/visual_shader_nodes.cpp
@@ -5579,3 +5579,127 @@ VisualShaderNodeMultiplyAdd::VisualShaderNodeMultiplyAdd() {
set_input_port_default_value(1, 0.0);
set_input_port_default_value(2, 0.0);
}
+
+////////////// Billboard
+
+String VisualShaderNodeBillboard::get_caption() const {
+ return "GetBillboardMatrix";
+}
+
+int VisualShaderNodeBillboard::get_input_port_count() const {
+ return 0;
+}
+
+VisualShaderNodeBillboard::PortType VisualShaderNodeBillboard::get_input_port_type(int p_port) const {
+ return PORT_TYPE_SCALAR;
+}
+
+String VisualShaderNodeBillboard::get_input_port_name(int p_port) const {
+ return "";
+}
+
+int VisualShaderNodeBillboard::get_output_port_count() const {
+ return 1;
+}
+
+VisualShaderNodeBillboard::PortType VisualShaderNodeBillboard::get_output_port_type(int p_port) const {
+ return PORT_TYPE_TRANSFORM;
+}
+
+String VisualShaderNodeBillboard::get_output_port_name(int p_port) const {
+ return "model_view_matrix";
+}
+
+String VisualShaderNodeBillboard::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const {
+ String code;
+
+ switch (billboard_type) {
+ case BILLBOARD_TYPE_ENABLED:
+ code += "\t{\n";
+ code += "\t\tmat4 __mvm = INV_CAMERA_MATRIX * mat4(CAMERA_MATRIX[0], CAMERA_MATRIX[1], CAMERA_MATRIX[2], WORLD_MATRIX[3]);\n";
+ if (keep_scale) {
+ code += "\t\t__mvm = __mvm * mat4(vec4(length(WORLD_MATRIX[0].xyz), 0.0, 0.0, 0.0), vec4(0.0, length(WORLD_MATRIX[1].xyz), 0.0, 0.0), vec4(0.0, 0.0, length(WORLD_MATRIX[2].xyz), 0.0), vec4(0.0, 0.0, 0.0, 1.0));\n";
+ }
+ code += "\t\t" + p_output_vars[0] + " = __mvm;\n";
+ code += "\t}\n";
+ break;
+ case BILLBOARD_TYPE_FIXED_Y:
+ code += "\t{\n";
+ code += "\t\tmat4 __mvm = INV_CAMERA_MATRIX * mat4(CAMERA_MATRIX[0], WORLD_MATRIX[1], vec4(normalize(cross(CAMERA_MATRIX[0].xyz, WORLD_MATRIX[1].xyz)), 0.0), WORLD_MATRIX[3]);\n";
+ if (keep_scale) {
+ code += "\t\t__mvm = __mvm * mat4(vec4(length(WORLD_MATRIX[0].xyz), 0.0, 0.0, 0.0), vec4(0.0, 1.0, 0.0, 0.0), vec4(0.0, 0.0, length(WORLD_MATRIX[2].xyz), 0.0), vec4(0.0, 0.0, 0.0, 1.0));\n";
+ } else {
+ code += "\t\t__mvm = __mvm * mat4(vec4(1.0, 0.0, 0.0, 0.0), vec4(0.0, 1.0 / length(WORLD_MATRIX[1].xyz), 0.0, 0.0), vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0));\n";
+ }
+ code += "\t\t" + p_output_vars[0] + " = __mvm;\n";
+ code += "\t}\n";
+ break;
+ case BILLBOARD_TYPE_PARTICLES:
+ code += "\t{\n";
+ code += "\t\tmat4 __wm = mat4(normalize(CAMERA_MATRIX[0]) * length(WORLD_MATRIX[0]), normalize(CAMERA_MATRIX[1]) * length(WORLD_MATRIX[0]), normalize(CAMERA_MATRIX[2]) * length(WORLD_MATRIX[2]), WORLD_MATRIX[3]);\n";
+ code += "\t\t__wm = __wm * mat4(vec4(cos(INSTANCE_CUSTOM.x), -sin(INSTANCE_CUSTOM.x), 0.0, 0.0), vec4(sin(INSTANCE_CUSTOM.x), cos(INSTANCE_CUSTOM.x), 0.0, 0.0), vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0));\n";
+ code += "\t\t" + p_output_vars[0] + " = INV_CAMERA_MATRIX * __wm;\n";
+ code += "\t}\n";
+ break;
+ default:
+ code += "\t" + p_output_vars[0] + " = mat4(1.0);\n";
+ break;
+ }
+
+ return code;
+}
+
+bool VisualShaderNodeBillboard::is_show_prop_names() const {
+ return true;
+}
+
+void VisualShaderNodeBillboard::set_billboard_type(BillboardType p_billboard_type) {
+ ERR_FAIL_INDEX((int)p_billboard_type, BILLBOARD_TYPE_MAX);
+ billboard_type = p_billboard_type;
+ simple_decl = bool(billboard_type == BILLBOARD_TYPE_DISABLED);
+ set_disabled(simple_decl);
+ emit_changed();
+}
+
+VisualShaderNodeBillboard::BillboardType VisualShaderNodeBillboard::get_billboard_type() const {
+ return billboard_type;
+}
+
+void VisualShaderNodeBillboard::set_keep_scale_enabled(bool p_enabled) {
+ keep_scale = p_enabled;
+ emit_changed();
+}
+
+bool VisualShaderNodeBillboard::is_keep_scale_enabled() const {
+ return keep_scale;
+}
+
+Vector<StringName> VisualShaderNodeBillboard::get_editable_properties() const {
+ Vector<StringName> props;
+ props.push_back("billboard_type");
+ if (billboard_type == BILLBOARD_TYPE_ENABLED || billboard_type == BILLBOARD_TYPE_FIXED_Y) {
+ props.push_back("keep_scale");
+ }
+ return props;
+}
+
+void VisualShaderNodeBillboard::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_billboard_type", "billboard_type"), &VisualShaderNodeBillboard::set_billboard_type);
+ ClassDB::bind_method(D_METHOD("get_billboard_type"), &VisualShaderNodeBillboard::get_billboard_type);
+
+ ClassDB::bind_method(D_METHOD("set_keep_scale_enabled", "enabled"), &VisualShaderNodeBillboard::set_keep_scale_enabled);
+ ClassDB::bind_method(D_METHOD("is_keep_scale_enabled"), &VisualShaderNodeBillboard::is_keep_scale_enabled);
+
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "billboard_type", PROPERTY_HINT_ENUM, "Disabled,Enabled,Y-Billboard,Particles"), "set_billboard_type", "get_billboard_type");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "keep_scale"), "set_keep_scale_enabled", "is_keep_scale_enabled");
+
+ BIND_ENUM_CONSTANT(BILLBOARD_TYPE_DISABLED);
+ BIND_ENUM_CONSTANT(BILLBOARD_TYPE_ENABLED);
+ BIND_ENUM_CONSTANT(BILLBOARD_TYPE_FIXED_Y);
+ BIND_ENUM_CONSTANT(BILLBOARD_TYPE_PARTICLES);
+ BIND_ENUM_CONSTANT(BILLBOARD_TYPE_MAX);
+}
+
+VisualShaderNodeBillboard::VisualShaderNodeBillboard() {
+ simple_decl = false;
+}
diff --git a/scene/resources/visual_shader_nodes.h b/scene/resources/visual_shader_nodes.h
index 50329d122d..1c70459e3b 100644
--- a/scene/resources/visual_shader_nodes.h
+++ b/scene/resources/visual_shader_nodes.h
@@ -2217,4 +2217,51 @@ public:
VARIANT_ENUM_CAST(VisualShaderNodeMultiplyAdd::OpType)
+class VisualShaderNodeBillboard : public VisualShaderNode {
+ GDCLASS(VisualShaderNodeBillboard, VisualShaderNode);
+
+public:
+ enum BillboardType {
+ BILLBOARD_TYPE_DISABLED,
+ BILLBOARD_TYPE_ENABLED,
+ BILLBOARD_TYPE_FIXED_Y,
+ BILLBOARD_TYPE_PARTICLES,
+ BILLBOARD_TYPE_MAX,
+ };
+
+protected:
+ BillboardType billboard_type = BILLBOARD_TYPE_ENABLED;
+ bool keep_scale = false;
+
+protected:
+ static void _bind_methods();
+
+public:
+ virtual String get_caption() const override;
+
+ virtual int get_input_port_count() const override;
+ virtual PortType get_input_port_type(int p_port) const override;
+ virtual String get_input_port_name(int p_port) const override;
+
+ virtual int get_output_port_count() const override;
+ virtual PortType get_output_port_type(int p_port) const override;
+ virtual String get_output_port_name(int p_port) const override;
+
+ virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override;
+
+ virtual bool is_show_prop_names() const override;
+
+ void set_billboard_type(BillboardType p_billboard_type);
+ BillboardType get_billboard_type() const;
+
+ void set_keep_scale_enabled(bool p_enabled);
+ bool is_keep_scale_enabled() const;
+
+ virtual Vector<StringName> get_editable_properties() const override;
+
+ VisualShaderNodeBillboard();
+};
+
+VARIANT_ENUM_CAST(VisualShaderNodeBillboard::BillboardType)
+
#endif // VISUAL_SHADER_NODES_H
diff --git a/servers/rendering/renderer_canvas_render.h b/servers/rendering/renderer_canvas_render.h
index 0e9ef616cb..0266e137c0 100644
--- a/servers/rendering/renderer_canvas_render.h
+++ b/servers/rendering/renderer_canvas_render.h
@@ -420,7 +420,6 @@ public:
if (found_xform) {
r = xf.xform(r);
- found_xform = false;
}
if (first) {
diff --git a/servers/rendering/renderer_rd/renderer_compositor_rd.cpp b/servers/rendering/renderer_rd/renderer_compositor_rd.cpp
index 0012ba9c27..1337d36762 100644
--- a/servers/rendering/renderer_rd/renderer_compositor_rd.cpp
+++ b/servers/rendering/renderer_rd/renderer_compositor_rd.cpp
@@ -31,6 +31,7 @@
#include "renderer_compositor_rd.h"
#include "core/config/project_settings.h"
+#include "core/os/dir_access.h"
void RendererCompositorRD::prepare_for_blitting_render_targets() {
RD::get_singleton()->prepare_screen_for_drawing();
@@ -155,6 +156,43 @@ void RendererCompositorRD::finalize() {
RendererCompositorRD *RendererCompositorRD::singleton = nullptr;
RendererCompositorRD::RendererCompositorRD() {
+ {
+ String shader_cache_dir = Engine::get_singleton()->get_shader_cache_path();
+ if (shader_cache_dir == String()) {
+ shader_cache_dir = "user://";
+ }
+ DirAccessRef da = DirAccess::open(shader_cache_dir);
+ if (!da) {
+ ERR_PRINT("Can't create shader cache folder, no shader caching will happen: " + shader_cache_dir);
+ } else {
+ Error err = da->change_dir("shader_cache");
+ if (err != OK) {
+ err = da->make_dir("shader_cache");
+ }
+ if (err != OK) {
+ ERR_PRINT("Can't create shader cache folder, no shader caching will happen: " + shader_cache_dir);
+ } else {
+ shader_cache_dir = shader_cache_dir.plus_file("shader_cache");
+
+ bool shader_cache_enabled = GLOBAL_GET("rendering/shader_compiler/shader_cache/enabled");
+ if (!Engine::get_singleton()->is_editor_hint() && !shader_cache_enabled) {
+ shader_cache_dir = String(); //disable only if not editor
+ }
+
+ if (shader_cache_dir != String()) {
+ bool compress = GLOBAL_GET("rendering/shader_compiler/shader_cache/compress");
+ bool use_zstd = GLOBAL_GET("rendering/shader_compiler/shader_cache/use_zstd_compression");
+ bool strip_debug = GLOBAL_GET("rendering/shader_compiler/shader_cache/strip_debug");
+
+ ShaderRD::set_shader_cache_dir(shader_cache_dir);
+ ShaderRD::set_shader_cache_save_compressed(compress);
+ ShaderRD::set_shader_cache_save_compressed_zstd(use_zstd);
+ ShaderRD::set_shader_cache_save_debug(!strip_debug);
+ }
+ }
+ }
+ }
+
singleton = this;
time = 0;
@@ -171,3 +209,7 @@ RendererCompositorRD::RendererCompositorRD() {
scene = memnew(RendererSceneRenderImplementation::RenderForwardClustered(storage));
}
}
+
+RendererCompositorRD::~RendererCompositorRD() {
+ ShaderRD::set_shader_cache_dir(String());
+}
diff --git a/servers/rendering/renderer_rd/renderer_compositor_rd.h b/servers/rendering/renderer_rd/renderer_compositor_rd.h
index 52552f7ee3..7a78322051 100644
--- a/servers/rendering/renderer_rd/renderer_compositor_rd.h
+++ b/servers/rendering/renderer_rd/renderer_compositor_rd.h
@@ -118,6 +118,6 @@ public:
static RendererCompositorRD *singleton;
RendererCompositorRD();
- ~RendererCompositorRD() {}
+ ~RendererCompositorRD();
};
#endif // RASTERIZER_RD_H
diff --git a/servers/rendering/renderer_rd/shader_compiler_rd.cpp b/servers/rendering/renderer_rd/shader_compiler_rd.cpp
index 056c8113a7..7deedb80c3 100644
--- a/servers/rendering/renderer_rd/shader_compiler_rd.cpp
+++ b/servers/rendering/renderer_rd/shader_compiler_rd.cpp
@@ -369,17 +369,24 @@ void ShaderCompilerRD::_dump_function_deps(const SL::ShaderNode *p_node, const S
ERR_FAIL_COND(fidx == -1);
+ Vector<StringName> uses_functions;
+
for (Set<StringName>::Element *E = p_node->functions[fidx].uses_function.front(); E; E = E->next()) {
- if (added.has(E->get())) {
+ uses_functions.push_back(E->get());
+ }
+ uses_functions.sort_custom<StringName::AlphCompare>(); //ensure order is deterministic so the same shader is always produced
+
+ for (int k = 0; k < uses_functions.size(); k++) {
+ if (added.has(uses_functions[k])) {
continue; //was added already
}
- _dump_function_deps(p_node, E->get(), p_func_code, r_to_add, added);
+ _dump_function_deps(p_node, uses_functions[k], p_func_code, r_to_add, added);
SL::FunctionNode *fnode = nullptr;
for (int i = 0; i < p_node->functions.size(); i++) {
- if (p_node->functions[i].name == E->get()) {
+ if (p_node->functions[i].name == uses_functions[k]) {
fnode = p_node->functions[i].function;
break;
}
@@ -427,9 +434,9 @@ void ShaderCompilerRD::_dump_function_deps(const SL::ShaderNode *p_node, const S
header += ")\n";
r_to_add += header;
- r_to_add += p_func_code[E->get()];
+ r_to_add += p_func_code[uses_functions[k]];
- added.insert(E->get());
+ added.insert(uses_functions[k]);
}
}
@@ -581,63 +588,74 @@ String ShaderCompilerRD::_dump_node_code(const SL::Node *p_node, int p_level, Ge
uniform_defines.resize(max_uniforms);
bool uses_uniforms = false;
+ Vector<StringName> uniform_names;
+
for (Map<StringName, SL::ShaderNode::Uniform>::Element *E = pnode->uniforms.front(); E; E = E->next()) {
+ uniform_names.push_back(E->key());
+ }
+
+ uniform_names.sort_custom<StringName::AlphCompare>(); //ensure order is deterministic so the same shader is always produced
+
+ for (int k = 0; k < uniform_names.size(); k++) {
+ StringName uniform_name = uniform_names[k];
+ const SL::ShaderNode::Uniform &uniform = pnode->uniforms[uniform_name];
+
String ucode;
- if (E->get().scope == SL::ShaderNode::Uniform::SCOPE_INSTANCE) {
+ if (uniform.scope == SL::ShaderNode::Uniform::SCOPE_INSTANCE) {
//insert, but don't generate any code.
- p_actions.uniforms->insert(E->key(), E->get());
+ p_actions.uniforms->insert(uniform_name, uniform);
continue; //instances are indexed directly, dont need index uniforms
}
- if (SL::is_sampler_type(E->get().type)) {
- ucode = "layout(set = " + itos(actions.texture_layout_set) + ", binding = " + itos(actions.base_texture_binding_index + E->get().texture_order) + ") uniform ";
+ if (SL::is_sampler_type(uniform.type)) {
+ ucode = "layout(set = " + itos(actions.texture_layout_set) + ", binding = " + itos(actions.base_texture_binding_index + uniform.texture_order) + ") uniform ";
}
- bool is_buffer_global = !SL::is_sampler_type(E->get().type) && E->get().scope == SL::ShaderNode::Uniform::SCOPE_GLOBAL;
+ bool is_buffer_global = !SL::is_sampler_type(uniform.type) && uniform.scope == SL::ShaderNode::Uniform::SCOPE_GLOBAL;
if (is_buffer_global) {
//this is an integer to index the global table
ucode += _typestr(ShaderLanguage::TYPE_UINT);
} else {
- ucode += _prestr(E->get().precision);
- ucode += _typestr(E->get().type);
+ ucode += _prestr(uniform.precision);
+ ucode += _typestr(uniform.type);
}
- ucode += " " + _mkid(E->key());
+ ucode += " " + _mkid(uniform_name);
ucode += ";\n";
- if (SL::is_sampler_type(E->get().type)) {
+ if (SL::is_sampler_type(uniform.type)) {
for (int j = 0; j < STAGE_MAX; j++) {
r_gen_code.stage_globals[j] += ucode;
}
GeneratedCode::Texture texture;
- texture.name = E->key();
- texture.hint = E->get().hint;
- texture.type = E->get().type;
- texture.filter = E->get().filter;
- texture.repeat = E->get().repeat;
- texture.global = E->get().scope == ShaderLanguage::ShaderNode::Uniform::SCOPE_GLOBAL;
+ texture.name = uniform_name;
+ texture.hint = uniform.hint;
+ texture.type = uniform.type;
+ texture.filter = uniform.filter;
+ texture.repeat = uniform.repeat;
+ texture.global = uniform.scope == ShaderLanguage::ShaderNode::Uniform::SCOPE_GLOBAL;
if (texture.global) {
r_gen_code.uses_global_textures = true;
}
- r_gen_code.texture_uniforms.write[E->get().texture_order] = texture;
+ r_gen_code.texture_uniforms.write[uniform.texture_order] = texture;
} else {
if (!uses_uniforms) {
uses_uniforms = true;
}
- uniform_defines.write[E->get().order] = ucode;
+ uniform_defines.write[uniform.order] = ucode;
if (is_buffer_global) {
//globals are indices into the global table
- uniform_sizes.write[E->get().order] = _get_datatype_size(ShaderLanguage::TYPE_UINT);
- uniform_alignments.write[E->get().order] = _get_datatype_alignment(ShaderLanguage::TYPE_UINT);
+ uniform_sizes.write[uniform.order] = _get_datatype_size(ShaderLanguage::TYPE_UINT);
+ uniform_alignments.write[uniform.order] = _get_datatype_alignment(ShaderLanguage::TYPE_UINT);
} else {
- uniform_sizes.write[E->get().order] = _get_datatype_size(E->get().type);
- uniform_alignments.write[E->get().order] = _get_datatype_alignment(E->get().type);
+ uniform_sizes.write[uniform.order] = _get_datatype_size(uniform.type);
+ uniform_alignments.write[uniform.order] = _get_datatype_alignment(uniform.type);
}
}
- p_actions.uniforms->insert(E->key(), E->get());
+ p_actions.uniforms->insert(uniform_name, uniform);
}
for (int i = 0; i < max_uniforms; i++) {
@@ -704,21 +722,32 @@ String ShaderCompilerRD::_dump_node_code(const SL::Node *p_node, int p_level, Ge
List<Pair<StringName, SL::ShaderNode::Varying>> var_frag_to_light;
+ Vector<StringName> varying_names;
+
for (Map<StringName, SL::ShaderNode::Varying>::Element *E = pnode->varyings.front(); E; E = E->next()) {
- if (E->get().stage == SL::ShaderNode::Varying::STAGE_FRAGMENT_TO_LIGHT || E->get().stage == SL::ShaderNode::Varying::STAGE_FRAGMENT) {
- var_frag_to_light.push_back(Pair<StringName, SL::ShaderNode::Varying>(E->key(), E->get()));
- fragment_varyings.insert(E->key());
+ varying_names.push_back(E->key());
+ }
+
+ varying_names.sort_custom<StringName::AlphCompare>(); //ensure order is deterministic so the same shader is always produced
+
+ for (int k = 0; k < varying_names.size(); k++) {
+ StringName varying_name = varying_names[k];
+ const SL::ShaderNode::Varying &varying = pnode->varyings[varying_name];
+
+ if (varying.stage == SL::ShaderNode::Varying::STAGE_FRAGMENT_TO_LIGHT || varying.stage == SL::ShaderNode::Varying::STAGE_FRAGMENT) {
+ var_frag_to_light.push_back(Pair<StringName, SL::ShaderNode::Varying>(varying_name, varying));
+ fragment_varyings.insert(varying_name);
continue;
}
String vcode;
- String interp_mode = _interpstr(E->get().interpolation);
- vcode += _prestr(E->get().precision);
- vcode += _typestr(E->get().type);
- vcode += " " + _mkid(E->key());
- if (E->get().array_size > 0) {
+ String interp_mode = _interpstr(varying.interpolation);
+ vcode += _prestr(varying.precision);
+ vcode += _typestr(varying.type);
+ vcode += " " + _mkid(varying_name);
+ if (varying.array_size > 0) {
vcode += "[";
- vcode += itos(E->get().array_size);
+ vcode += itos(varying.array_size);
vcode += "]";
}
vcode += ";\n";
diff --git a/servers/rendering/renderer_rd/shader_rd.cpp b/servers/rendering/renderer_rd/shader_rd.cpp
index f7242a2b17..6f29ff42bc 100644
--- a/servers/rendering/renderer_rd/shader_rd.cpp
+++ b/servers/rendering/renderer_rd/shader_rd.cpp
@@ -30,8 +30,12 @@
#include "shader_rd.h"
+#include "core/io/compression.h"
+#include "core/os/dir_access.h"
+#include "core/os/file_access.h"
#include "renderer_compositor_rd.h"
#include "servers/rendering/rendering_device.h"
+#include "thirdparty/misc/smolv.h"
void ShaderRD::_add_stage(const char *p_code, StageType p_stage_type) {
Vector<String> lines = String(p_code).split("\n");
@@ -97,6 +101,7 @@ void ShaderRD::_add_stage(const char *p_code, StageType p_stage_type) {
void ShaderRD::setup(const char *p_vertex_code, const char *p_fragment_code, const char *p_compute_code, const char *p_name) {
name = p_name;
+
if (p_compute_code) {
_add_stage(p_compute_code, STAGE_TYPE_COMPUTE);
is_compute = true;
@@ -109,6 +114,18 @@ void ShaderRD::setup(const char *p_vertex_code, const char *p_fragment_code, con
_add_stage(p_fragment_code, STAGE_TYPE_FRAGMENT);
}
}
+
+ StringBuilder tohash;
+ tohash.append("[VersionKey]");
+ tohash.append(RenderingDevice::get_singleton()->shader_get_cache_key());
+ tohash.append("[Vertex]");
+ tohash.append(p_vertex_code ? p_vertex_code : "");
+ tohash.append("[Fragment]");
+ tohash.append(p_fragment_code ? p_fragment_code : "");
+ tohash.append("[Compute]");
+ tohash.append(p_compute_code ? p_compute_code : "");
+
+ base_sha256 = tohash.as_string().sha256_text();
}
RID ShaderRD::version_create() {
@@ -131,6 +148,9 @@ void ShaderRD::_clear_version(Version *p_version) {
}
memdelete_arr(p_version->variants);
+ if (p_version->variant_stages) {
+ memdelete_arr(p_version->variant_stages);
+ }
p_version->variants = nullptr;
}
}
@@ -183,7 +203,7 @@ void ShaderRD::_compile_variant(uint32_t p_variant, Version *p_version) {
return; //variant is disabled, return
}
- Vector<RD::ShaderStageData> stages;
+ Vector<RD::ShaderStageData> &stages = p_version->variant_stages[p_variant];
String error;
String current_source;
@@ -313,6 +333,197 @@ RS::ShaderNativeSourceCode ShaderRD::version_get_native_source_code(RID p_versio
return source_code;
}
+String ShaderRD::_version_get_sha1(Version *p_version) const {
+ StringBuilder hash_build;
+
+ hash_build.append("[uniforms]");
+ hash_build.append(p_version->uniforms.get_data());
+ hash_build.append("[vertex_globals]");
+ hash_build.append(p_version->vertex_globals.get_data());
+ hash_build.append("[fragment_globals]");
+ hash_build.append(p_version->fragment_globals.get_data());
+ hash_build.append("[compute_globals]");
+ hash_build.append(p_version->compute_globals.get_data());
+
+ Vector<StringName> code_sections;
+ for (Map<StringName, CharString>::Element *E = p_version->code_sections.front(); E; E = E->next()) {
+ code_sections.push_back(E->key());
+ }
+ code_sections.sort_custom<StringName::AlphCompare>();
+
+ for (int i = 0; i < code_sections.size(); i++) {
+ hash_build.append(String("[code:") + String(code_sections[i]) + "]");
+ hash_build.append(p_version->code_sections[code_sections[i]].get_data());
+ }
+ for (int i = 0; i < p_version->custom_defines.size(); i++) {
+ hash_build.append("[custom_defines:" + itos(i) + "]");
+ hash_build.append(p_version->custom_defines[i].get_data());
+ }
+
+ return hash_build.as_string().sha1_text();
+}
+
+static const char *shader_file_header = "GDSC";
+static const uint32_t cache_file_version = 1;
+
+bool ShaderRD::_load_from_cache(Version *p_version) {
+ String sha1 = _version_get_sha1(p_version);
+ String path = shader_cache_dir.plus_file(name).plus_file(base_sha256).plus_file(sha1) + ".cache";
+
+ uint64_t time_from = OS::get_singleton()->get_ticks_usec();
+
+ FileAccessRef f = FileAccess::open(path, FileAccess::READ);
+ if (!f) {
+ return false;
+ }
+
+ char header[5] = { 0, 0, 0, 0, 0 };
+ f->get_buffer((uint8_t *)header, 4);
+ ERR_FAIL_COND_V(header != String(shader_file_header), false);
+
+ uint32_t file_version = f->get_32();
+ if (file_version != cache_file_version) {
+ return false; // wrong version
+ }
+
+ uint32_t variant_count = f->get_32();
+
+ ERR_FAIL_COND_V(variant_count != (uint32_t)variant_defines.size(), false); //should not happen but check
+
+ bool success = true;
+ for (uint32_t i = 0; i < variant_count; i++) {
+ uint32_t stage_count = f->get_32();
+ p_version->variant_stages[i].resize(stage_count);
+ for (uint32_t j = 0; j < stage_count; j++) {
+ p_version->variant_stages[i].write[j].shader_stage = RD::ShaderStage(f->get_32());
+
+ int compression = f->get_32();
+ uint32_t length = f->get_32();
+
+ if (compression == 0) {
+ Vector<uint8_t> data;
+ data.resize(length);
+
+ f->get_buffer(data.ptrw(), length);
+
+ p_version->variant_stages[i].write[j].spir_v = data;
+ } else {
+ Vector<uint8_t> data;
+
+ if (compression == 2) {
+ //zstd
+ int smol_length = f->get_32();
+ Vector<uint8_t> zstd_data;
+
+ zstd_data.resize(smol_length);
+ f->get_buffer(zstd_data.ptrw(), smol_length);
+
+ data.resize(length);
+ Compression::decompress(data.ptrw(), data.size(), zstd_data.ptr(), zstd_data.size(), Compression::MODE_ZSTD);
+
+ } else {
+ data.resize(length);
+ f->get_buffer(data.ptrw(), length);
+ }
+
+ Vector<uint8_t> spirv;
+ uint32_t spirv_size = smolv::GetDecodedBufferSize(data.ptr(), data.size());
+ spirv.resize(spirv_size);
+ if (!smolv::Decode(data.ptr(), data.size(), spirv.ptrw(), spirv_size)) {
+ ERR_PRINT("Malformed smolv input uncompressing shader " + name + ", variant #" + itos(i) + " stage :" + itos(j));
+ success = false;
+ break;
+ }
+ p_version->variant_stages[i].write[j].spir_v = spirv;
+ }
+ }
+ }
+
+ if (!success) {
+ for (uint32_t i = 0; i < variant_count; i++) {
+ p_version->variant_stages[i].resize(0);
+ }
+ return false;
+ }
+
+ float time_ms = double(OS::get_singleton()->get_ticks_usec() - time_from) / 1000.0;
+
+ print_verbose("Shader cache load success '" + path + "' " + rtos(time_ms) + "ms.");
+
+ for (uint32_t i = 0; i < variant_count; i++) {
+ RID shader = RD::get_singleton()->shader_create(p_version->variant_stages[i]);
+ {
+ MutexLock lock(variant_set_mutex);
+ p_version->variants[i] = shader;
+ }
+ }
+
+ memdelete_arr(p_version->variant_stages); //clear stages
+ p_version->variant_stages = nullptr;
+ p_version->valid = true;
+ return true;
+}
+
+void ShaderRD::_save_to_cache(Version *p_version) {
+ String sha1 = _version_get_sha1(p_version);
+ String path = shader_cache_dir.plus_file(name).plus_file(base_sha256).plus_file(sha1) + ".cache";
+
+ FileAccessRef f = FileAccess::open(path, FileAccess::WRITE);
+ ERR_FAIL_COND(!f);
+ f->store_buffer((const uint8_t *)shader_file_header, 4);
+ f->store_32(cache_file_version); //file version
+ uint32_t variant_count = variant_defines.size();
+ f->store_32(variant_count); //variant count
+
+ for (uint32_t i = 0; i < variant_count; i++) {
+ f->store_32(p_version->variant_stages[i].size()); //stage count
+ for (int j = 0; j < p_version->variant_stages[i].size(); j++) {
+ f->store_32(p_version->variant_stages[i][j].shader_stage); //stage count
+ Vector<uint8_t> spirv = p_version->variant_stages[i][j].spir_v;
+
+ bool save_uncompressed = true;
+ if (shader_cache_save_compressed) {
+ smolv::ByteArray smolv;
+ bool strip_debug = !shader_cache_save_debug;
+ if (!smolv::Encode(spirv.ptr(), spirv.size(), smolv, strip_debug ? smolv::kEncodeFlagStripDebugInfo : 0)) {
+ ERR_PRINT("Error compressing shader " + name + ", variant #" + itos(i) + " stage :" + itos(i));
+ } else {
+ bool compress_zstd = shader_cache_save_compressed_zstd;
+
+ if (compress_zstd) {
+ Vector<uint8_t> zstd;
+ zstd.resize(Compression::get_max_compressed_buffer_size(smolv.size(), Compression::MODE_ZSTD));
+ int dst_size = Compression::compress(zstd.ptrw(), &smolv[0], smolv.size(), Compression::MODE_ZSTD);
+ if (dst_size >= 0 && (uint32_t)dst_size < smolv.size()) {
+ f->store_32(2); //compressed zstd
+ f->store_32(smolv.size()); //size of smolv buffer
+ f->store_32(dst_size); //size of smolv buffer
+ f->store_buffer(zstd.ptr(), dst_size); //smolv buffer
+ } else {
+ compress_zstd = false;
+ }
+ }
+
+ if (!compress_zstd) {
+ f->store_32(1); //compressed
+ f->store_32(smolv.size()); //size of smolv buffer
+ f->store_buffer(&smolv[0], smolv.size()); //smolv buffer
+ }
+ save_uncompressed = false;
+ }
+ }
+
+ if (save_uncompressed) {
+ f->store_32(0); //uncompressed
+ f->store_32(spirv.size()); //stage count
+ f->store_buffer(spirv.ptr(), spirv.size()); //stage count
+ }
+ }
+ }
+
+ f->close();
+}
+
void ShaderRD::_compile_version(Version *p_version) {
_clear_version(p_version);
@@ -320,6 +531,15 @@ void ShaderRD::_compile_version(Version *p_version) {
p_version->dirty = false;
p_version->variants = memnew_arr(RID, variant_defines.size());
+ typedef Vector<RD::ShaderStageData> ShaderStageArray;
+ p_version->variant_stages = memnew_arr(ShaderStageArray, variant_defines.size());
+
+ if (shader_cache_dir_valid) {
+ if (_load_from_cache(p_version)) {
+ return;
+ }
+ }
+
#if 1
RendererThreadPool::singleton->thread_work_pool.do_work(variant_defines.size(), this, &ShaderRD::_compile_variant, p_version);
@@ -351,10 +571,20 @@ void ShaderRD::_compile_version(Version *p_version) {
}
}
memdelete_arr(p_version->variants);
+ if (p_version->variant_stages) {
+ memdelete_arr(p_version->variant_stages);
+ }
p_version->variants = nullptr;
+ p_version->variant_stages = nullptr;
return;
+ } else if (shader_cache_dir_valid) {
+ //save shader cache
+ _save_to_cache(p_version);
}
+ memdelete_arr(p_version->variant_stages); //clear stages
+ p_version->variant_stages = nullptr;
+
p_version->valid = true;
}
@@ -443,6 +673,8 @@ bool ShaderRD::is_variant_enabled(int p_variant) const {
return variants_enabled[p_variant];
}
+bool ShaderRD::shader_cache_cleanup_on_start = false;
+
ShaderRD::ShaderRD() {
// Do not feel forced to use this, in most cases it makes little to no difference.
bool use_32_threads = false;
@@ -469,8 +701,64 @@ void ShaderRD::initialize(const Vector<String> &p_variant_defines, const String
variant_defines.push_back(p_variant_defines[i].utf8());
variants_enabled.push_back(true);
}
+
+ if (shader_cache_dir != String()) {
+ StringBuilder hash_build;
+
+ hash_build.append("[base_hash]");
+ hash_build.append(base_sha256);
+ hash_build.append("[general_defines]");
+ hash_build.append(general_defines.get_data());
+ for (int i = 0; i < variant_defines.size(); i++) {
+ hash_build.append("[variant_defines:" + itos(i) + "]");
+ hash_build.append(variant_defines[i].get_data());
+ }
+
+ base_sha256 = hash_build.as_string().sha256_text();
+
+ DirAccessRef d = DirAccess::open(shader_cache_dir);
+ ERR_FAIL_COND(!d);
+ if (d->change_dir(name) != OK) {
+ Error err = d->make_dir(name);
+ ERR_FAIL_COND(err != OK);
+ d->change_dir(name);
+ }
+
+ //erase other versions?
+ if (shader_cache_cleanup_on_start) {
+ }
+ //
+ if (d->change_dir(base_sha256) != OK) {
+ Error err = d->make_dir(base_sha256);
+ ERR_FAIL_COND(err != OK);
+ }
+ shader_cache_dir_valid = true;
+
+ print_verbose("Shader '" + name + "' SHA256: " + base_sha256);
+ }
+}
+
+void ShaderRD::set_shader_cache_dir(const String &p_dir) {
+ shader_cache_dir = p_dir;
+}
+
+void ShaderRD::set_shader_cache_save_compressed(bool p_enable) {
+ shader_cache_save_compressed = p_enable;
}
+void ShaderRD::set_shader_cache_save_compressed_zstd(bool p_enable) {
+ shader_cache_save_compressed_zstd = p_enable;
+}
+
+void ShaderRD::set_shader_cache_save_debug(bool p_enable) {
+ shader_cache_save_debug = p_enable;
+}
+
+String ShaderRD::shader_cache_dir;
+bool ShaderRD::shader_cache_save_compressed = true;
+bool ShaderRD::shader_cache_save_compressed_zstd = true;
+bool ShaderRD::shader_cache_save_debug = true;
+
ShaderRD::~ShaderRD() {
List<RID> remaining;
version_owner.get_owned_list(&remaining);
diff --git a/servers/rendering/renderer_rd/shader_rd.h b/servers/rendering/renderer_rd/shader_rd.h
index f20d539621..9a68e02007 100644
--- a/servers/rendering/renderer_rd/shader_rd.h
+++ b/servers/rendering/renderer_rd/shader_rd.h
@@ -59,7 +59,8 @@ class ShaderRD {
Map<StringName, CharString> code_sections;
Vector<CharString> custom_defines;
- RID *variants; //same size as version defines
+ Vector<RD::ShaderStageData> *variant_stages = nullptr;
+ RID *variants = nullptr; //same size as version defines
bool valid;
bool dirty;
@@ -96,10 +97,19 @@ class ShaderRD {
bool is_compute = false;
- const char *name;
+ String name;
CharString base_compute_defines;
+ String base_sha256;
+
+ static String shader_cache_dir;
+ static bool shader_cache_cleanup_on_start;
+ static bool shader_cache_save_compressed;
+ static bool shader_cache_save_compressed_zstd;
+ static bool shader_cache_save_debug;
+ bool shader_cache_dir_valid = false;
+
enum StageType {
STAGE_TYPE_VERTEX,
STAGE_TYPE_FRAGMENT,
@@ -113,6 +123,10 @@ class ShaderRD {
void _add_stage(const char *p_code, StageType p_stage_type);
+ String _version_get_sha1(Version *p_version) const;
+ bool _load_from_cache(Version *p_version);
+ void _save_to_cache(Version *p_version);
+
protected:
ShaderRD();
void setup(const char *p_vertex_code, const char *p_fragment_code, const char *p_compute_code, const char *p_name);
@@ -148,6 +162,11 @@ public:
void set_variant_enabled(int p_variant, bool p_enabled);
bool is_variant_enabled(int p_variant) const;
+ static void set_shader_cache_dir(const String &p_dir);
+ static void set_shader_cache_save_compressed(bool p_enable);
+ static void set_shader_cache_save_compressed_zstd(bool p_enable);
+ static void set_shader_cache_save_debug(bool p_enable);
+
RS::ShaderNativeSourceCode version_get_native_source_code(RID p_version);
void initialize(const Vector<String> &p_variant_defines, const String &p_general_defines = "");
diff --git a/servers/rendering/rendering_device.cpp b/servers/rendering/rendering_device.cpp
index e6ad001807..056cec4c1f 100644
--- a/servers/rendering/rendering_device.cpp
+++ b/servers/rendering/rendering_device.cpp
@@ -40,6 +40,7 @@ RenderingDevice *RenderingDevice::get_singleton() {
RenderingDevice::ShaderCompileFunction RenderingDevice::compile_function = nullptr;
RenderingDevice::ShaderCacheFunction RenderingDevice::cache_function = nullptr;
+RenderingDevice::ShaderGetCacheKeyFunction RenderingDevice::get_cache_key_function = nullptr;
void RenderingDevice::shader_set_compile_function(ShaderCompileFunction p_function) {
compile_function = p_function;
@@ -49,6 +50,10 @@ void RenderingDevice::shader_set_cache_function(ShaderCacheFunction p_function)
cache_function = p_function;
}
+void RenderingDevice::shader_set_get_cache_key_function(ShaderGetCacheKeyFunction p_function) {
+ get_cache_key_function = p_function;
+}
+
Vector<uint8_t> RenderingDevice::shader_compile_from_source(ShaderStage p_stage, const String &p_source_code, ShaderLanguage p_language, String *r_error, bool p_allow_cache) {
if (p_allow_cache && cache_function) {
Vector<uint8_t> cache = cache_function(p_stage, p_source_code, p_language);
@@ -62,6 +67,13 @@ Vector<uint8_t> RenderingDevice::shader_compile_from_source(ShaderStage p_stage,
return compile_function(p_stage, p_source_code, p_language, r_error, &device_capabilities);
}
+String RenderingDevice::shader_get_cache_key() const {
+ if (get_cache_key_function) {
+ return get_cache_key_function(&device_capabilities);
+ }
+ return String();
+}
+
RID RenderingDevice::_texture_create(const Ref<RDTextureFormat> &p_format, const Ref<RDTextureView> &p_view, const TypedArray<PackedByteArray> &p_data) {
ERR_FAIL_COND_V(p_format.is_null(), RID());
ERR_FAIL_COND_V(p_view.is_null(), RID());
diff --git a/servers/rendering/rendering_device.h b/servers/rendering/rendering_device.h
index 27bded9810..4dcb9b963e 100644
--- a/servers/rendering/rendering_device.h
+++ b/servers/rendering/rendering_device.h
@@ -103,12 +103,14 @@ public:
bool supports_multiview = false; // If true this device supports multiview options
};
+ typedef String (*ShaderGetCacheKeyFunction)(const Capabilities *p_capabilities);
typedef Vector<uint8_t> (*ShaderCompileFunction)(ShaderStage p_stage, const String &p_source_code, ShaderLanguage p_language, String *r_error, const Capabilities *p_capabilities);
typedef Vector<uint8_t> (*ShaderCacheFunction)(ShaderStage p_stage, const String &p_source_code, ShaderLanguage p_language);
private:
static ShaderCompileFunction compile_function;
static ShaderCacheFunction cache_function;
+ static ShaderGetCacheKeyFunction get_cache_key_function;
static RenderingDevice *singleton;
@@ -635,9 +637,11 @@ public:
const Capabilities *get_device_capabilities() const { return &device_capabilities; };
virtual Vector<uint8_t> shader_compile_from_source(ShaderStage p_stage, const String &p_source_code, ShaderLanguage p_language = SHADER_LANGUAGE_GLSL, String *r_error = nullptr, bool p_allow_cache = true);
+ virtual String shader_get_cache_key() const;
static void shader_set_compile_function(ShaderCompileFunction p_function);
static void shader_set_cache_function(ShaderCacheFunction p_function);
+ static void shader_set_get_cache_key_function(ShaderGetCacheKeyFunction p_function);
struct ShaderStageData {
ShaderStage shader_stage;
diff --git a/servers/rendering_server.cpp b/servers/rendering_server.cpp
index a9601fd661..4741e90a81 100644
--- a/servers/rendering_server.cpp
+++ b/servers/rendering_server.cpp
@@ -2306,6 +2306,12 @@ RenderingServer::RenderingServer() {
"rendering/vulkan/rendering/back_end",
PROPERTY_HINT_ENUM, "ForwardClustered,ForwardMobile"));
+ GLOBAL_DEF("rendering/shader_compiler/shader_cache/enabled", true);
+ GLOBAL_DEF("rendering/shader_compiler/shader_cache/compress", true);
+ GLOBAL_DEF("rendering/shader_compiler/shader_cache/use_zstd_compression", true);
+ GLOBAL_DEF("rendering/shader_compiler/shader_cache/strip_debug", false);
+ GLOBAL_DEF("rendering/shader_compiler/shader_cache/strip_debug.release", true);
+
GLOBAL_DEF("rendering/reflections/sky_reflections/roughness_layers", 8);
GLOBAL_DEF("rendering/reflections/sky_reflections/texture_array_reflections", true);
GLOBAL_DEF("rendering/reflections/sky_reflections/texture_array_reflections.mobile", false);
diff --git a/thirdparty/README.md b/thirdparty/README.md
index 1500e8d656..03a2ddf5e4 100644
--- a/thirdparty/README.md
+++ b/thirdparty/README.md
@@ -469,6 +469,10 @@ Collection of single-file libraries used in Godot components.
* Version: git (2f625846a775501fb69456567409a8b12f10ea25, 2012)
* License: BSD-3-Clause
* Modifications: use `const char*` instead of `char*` for input string
+- `smolv.h`
+ * Upstream: https://github.com/aras-p/smol-v
+ * Version: git (4b52c165c13763051a18e80ffbc2ee436314ceb2, 2020)
+ * License: Public Domain or MIT
- `stb_rect_pack.h`
* Upstream: https://github.com/nothings/stb
* Version: 1.00 (2bb4a0accd4003c1db4c24533981e01b1adfd656, 2019)
@@ -731,3 +735,4 @@ Files extracted from upstream source:
- lib/{common/,compress/,decompress/,zstd.h}
- LICENSE
+
diff --git a/thirdparty/misc/smolv.cpp b/thirdparty/misc/smolv.cpp
new file mode 100644
index 0000000000..26ed7294f9
--- /dev/null
+++ b/thirdparty/misc/smolv.cpp
@@ -0,0 +1,2108 @@
+// smol-v - public domain - https://github.com/aras-p/smol-v
+// authored 2016-2020 by Aras Pranckevicius
+// no warranty implied; use at your own risk
+// See end of file for license information.
+
+#include "smolv.h"
+#include <stdint.h>
+#include <vector>
+#include <algorithm>
+#include <cstdio>
+#include <cstring>
+
+#if !defined(_MSC_VER) && __cplusplus < 201103L
+#define static_assert(x,y)
+#endif
+
+#define _SMOLV_ARRAY_SIZE(a) (sizeof(a)/sizeof((a)[0]))
+
+// --------------------------------------------------------------------------------------------
+// Metadata about known SPIR-V operations
+
+enum SpvOp
+{
+ SpvOpNop = 0,
+ SpvOpUndef = 1,
+ SpvOpSourceContinued = 2,
+ SpvOpSource = 3,
+ SpvOpSourceExtension = 4,
+ SpvOpName = 5,
+ SpvOpMemberName = 6,
+ SpvOpString = 7,
+ SpvOpLine = 8,
+ SpvOpExtension = 10,
+ SpvOpExtInstImport = 11,
+ SpvOpExtInst = 12,
+ SpvOpVectorShuffleCompact = 13, // not in SPIR-V, added for SMOL-V!
+ SpvOpMemoryModel = 14,
+ SpvOpEntryPoint = 15,
+ SpvOpExecutionMode = 16,
+ SpvOpCapability = 17,
+ SpvOpTypeVoid = 19,
+ SpvOpTypeBool = 20,
+ SpvOpTypeInt = 21,
+ SpvOpTypeFloat = 22,
+ SpvOpTypeVector = 23,
+ SpvOpTypeMatrix = 24,
+ SpvOpTypeImage = 25,
+ SpvOpTypeSampler = 26,
+ SpvOpTypeSampledImage = 27,
+ SpvOpTypeArray = 28,
+ SpvOpTypeRuntimeArray = 29,
+ SpvOpTypeStruct = 30,
+ SpvOpTypeOpaque = 31,
+ SpvOpTypePointer = 32,
+ SpvOpTypeFunction = 33,
+ SpvOpTypeEvent = 34,
+ SpvOpTypeDeviceEvent = 35,
+ SpvOpTypeReserveId = 36,
+ SpvOpTypeQueue = 37,
+ SpvOpTypePipe = 38,
+ SpvOpTypeForwardPointer = 39,
+ SpvOpConstantTrue = 41,
+ SpvOpConstantFalse = 42,
+ SpvOpConstant = 43,
+ SpvOpConstantComposite = 44,
+ SpvOpConstantSampler = 45,
+ SpvOpConstantNull = 46,
+ SpvOpSpecConstantTrue = 48,
+ SpvOpSpecConstantFalse = 49,
+ SpvOpSpecConstant = 50,
+ SpvOpSpecConstantComposite = 51,
+ SpvOpSpecConstantOp = 52,
+ SpvOpFunction = 54,
+ SpvOpFunctionParameter = 55,
+ SpvOpFunctionEnd = 56,
+ SpvOpFunctionCall = 57,
+ SpvOpVariable = 59,
+ SpvOpImageTexelPointer = 60,
+ SpvOpLoad = 61,
+ SpvOpStore = 62,
+ SpvOpCopyMemory = 63,
+ SpvOpCopyMemorySized = 64,
+ SpvOpAccessChain = 65,
+ SpvOpInBoundsAccessChain = 66,
+ SpvOpPtrAccessChain = 67,
+ SpvOpArrayLength = 68,
+ SpvOpGenericPtrMemSemantics = 69,
+ SpvOpInBoundsPtrAccessChain = 70,
+ SpvOpDecorate = 71,
+ SpvOpMemberDecorate = 72,
+ SpvOpDecorationGroup = 73,
+ SpvOpGroupDecorate = 74,
+ SpvOpGroupMemberDecorate = 75,
+ SpvOpVectorExtractDynamic = 77,
+ SpvOpVectorInsertDynamic = 78,
+ SpvOpVectorShuffle = 79,
+ SpvOpCompositeConstruct = 80,
+ SpvOpCompositeExtract = 81,
+ SpvOpCompositeInsert = 82,
+ SpvOpCopyObject = 83,
+ SpvOpTranspose = 84,
+ SpvOpSampledImage = 86,
+ SpvOpImageSampleImplicitLod = 87,
+ SpvOpImageSampleExplicitLod = 88,
+ SpvOpImageSampleDrefImplicitLod = 89,
+ SpvOpImageSampleDrefExplicitLod = 90,
+ SpvOpImageSampleProjImplicitLod = 91,
+ SpvOpImageSampleProjExplicitLod = 92,
+ SpvOpImageSampleProjDrefImplicitLod = 93,
+ SpvOpImageSampleProjDrefExplicitLod = 94,
+ SpvOpImageFetch = 95,
+ SpvOpImageGather = 96,
+ SpvOpImageDrefGather = 97,
+ SpvOpImageRead = 98,
+ SpvOpImageWrite = 99,
+ SpvOpImage = 100,
+ SpvOpImageQueryFormat = 101,
+ SpvOpImageQueryOrder = 102,
+ SpvOpImageQuerySizeLod = 103,
+ SpvOpImageQuerySize = 104,
+ SpvOpImageQueryLod = 105,
+ SpvOpImageQueryLevels = 106,
+ SpvOpImageQuerySamples = 107,
+ SpvOpConvertFToU = 109,
+ SpvOpConvertFToS = 110,
+ SpvOpConvertSToF = 111,
+ SpvOpConvertUToF = 112,
+ SpvOpUConvert = 113,
+ SpvOpSConvert = 114,
+ SpvOpFConvert = 115,
+ SpvOpQuantizeToF16 = 116,
+ SpvOpConvertPtrToU = 117,
+ SpvOpSatConvertSToU = 118,
+ SpvOpSatConvertUToS = 119,
+ SpvOpConvertUToPtr = 120,
+ SpvOpPtrCastToGeneric = 121,
+ SpvOpGenericCastToPtr = 122,
+ SpvOpGenericCastToPtrExplicit = 123,
+ SpvOpBitcast = 124,
+ SpvOpSNegate = 126,
+ SpvOpFNegate = 127,
+ SpvOpIAdd = 128,
+ SpvOpFAdd = 129,
+ SpvOpISub = 130,
+ SpvOpFSub = 131,
+ SpvOpIMul = 132,
+ SpvOpFMul = 133,
+ SpvOpUDiv = 134,
+ SpvOpSDiv = 135,
+ SpvOpFDiv = 136,
+ SpvOpUMod = 137,
+ SpvOpSRem = 138,
+ SpvOpSMod = 139,
+ SpvOpFRem = 140,
+ SpvOpFMod = 141,
+ SpvOpVectorTimesScalar = 142,
+ SpvOpMatrixTimesScalar = 143,
+ SpvOpVectorTimesMatrix = 144,
+ SpvOpMatrixTimesVector = 145,
+ SpvOpMatrixTimesMatrix = 146,
+ SpvOpOuterProduct = 147,
+ SpvOpDot = 148,
+ SpvOpIAddCarry = 149,
+ SpvOpISubBorrow = 150,
+ SpvOpUMulExtended = 151,
+ SpvOpSMulExtended = 152,
+ SpvOpAny = 154,
+ SpvOpAll = 155,
+ SpvOpIsNan = 156,
+ SpvOpIsInf = 157,
+ SpvOpIsFinite = 158,
+ SpvOpIsNormal = 159,
+ SpvOpSignBitSet = 160,
+ SpvOpLessOrGreater = 161,
+ SpvOpOrdered = 162,
+ SpvOpUnordered = 163,
+ SpvOpLogicalEqual = 164,
+ SpvOpLogicalNotEqual = 165,
+ SpvOpLogicalOr = 166,
+ SpvOpLogicalAnd = 167,
+ SpvOpLogicalNot = 168,
+ SpvOpSelect = 169,
+ SpvOpIEqual = 170,
+ SpvOpINotEqual = 171,
+ SpvOpUGreaterThan = 172,
+ SpvOpSGreaterThan = 173,
+ SpvOpUGreaterThanEqual = 174,
+ SpvOpSGreaterThanEqual = 175,
+ SpvOpULessThan = 176,
+ SpvOpSLessThan = 177,
+ SpvOpULessThanEqual = 178,
+ SpvOpSLessThanEqual = 179,
+ SpvOpFOrdEqual = 180,
+ SpvOpFUnordEqual = 181,
+ SpvOpFOrdNotEqual = 182,
+ SpvOpFUnordNotEqual = 183,
+ SpvOpFOrdLessThan = 184,
+ SpvOpFUnordLessThan = 185,
+ SpvOpFOrdGreaterThan = 186,
+ SpvOpFUnordGreaterThan = 187,
+ SpvOpFOrdLessThanEqual = 188,
+ SpvOpFUnordLessThanEqual = 189,
+ SpvOpFOrdGreaterThanEqual = 190,
+ SpvOpFUnordGreaterThanEqual = 191,
+ SpvOpShiftRightLogical = 194,
+ SpvOpShiftRightArithmetic = 195,
+ SpvOpShiftLeftLogical = 196,
+ SpvOpBitwiseOr = 197,
+ SpvOpBitwiseXor = 198,
+ SpvOpBitwiseAnd = 199,
+ SpvOpNot = 200,
+ SpvOpBitFieldInsert = 201,
+ SpvOpBitFieldSExtract = 202,
+ SpvOpBitFieldUExtract = 203,
+ SpvOpBitReverse = 204,
+ SpvOpBitCount = 205,
+ SpvOpDPdx = 207,
+ SpvOpDPdy = 208,
+ SpvOpFwidth = 209,
+ SpvOpDPdxFine = 210,
+ SpvOpDPdyFine = 211,
+ SpvOpFwidthFine = 212,
+ SpvOpDPdxCoarse = 213,
+ SpvOpDPdyCoarse = 214,
+ SpvOpFwidthCoarse = 215,
+ SpvOpEmitVertex = 218,
+ SpvOpEndPrimitive = 219,
+ SpvOpEmitStreamVertex = 220,
+ SpvOpEndStreamPrimitive = 221,
+ SpvOpControlBarrier = 224,
+ SpvOpMemoryBarrier = 225,
+ SpvOpAtomicLoad = 227,
+ SpvOpAtomicStore = 228,
+ SpvOpAtomicExchange = 229,
+ SpvOpAtomicCompareExchange = 230,
+ SpvOpAtomicCompareExchangeWeak = 231,
+ SpvOpAtomicIIncrement = 232,
+ SpvOpAtomicIDecrement = 233,
+ SpvOpAtomicIAdd = 234,
+ SpvOpAtomicISub = 235,
+ SpvOpAtomicSMin = 236,
+ SpvOpAtomicUMin = 237,
+ SpvOpAtomicSMax = 238,
+ SpvOpAtomicUMax = 239,
+ SpvOpAtomicAnd = 240,
+ SpvOpAtomicOr = 241,
+ SpvOpAtomicXor = 242,
+ SpvOpPhi = 245,
+ SpvOpLoopMerge = 246,
+ SpvOpSelectionMerge = 247,
+ SpvOpLabel = 248,
+ SpvOpBranch = 249,
+ SpvOpBranchConditional = 250,
+ SpvOpSwitch = 251,
+ SpvOpKill = 252,
+ SpvOpReturn = 253,
+ SpvOpReturnValue = 254,
+ SpvOpUnreachable = 255,
+ SpvOpLifetimeStart = 256,
+ SpvOpLifetimeStop = 257,
+ SpvOpGroupAsyncCopy = 259,
+ SpvOpGroupWaitEvents = 260,
+ SpvOpGroupAll = 261,
+ SpvOpGroupAny = 262,
+ SpvOpGroupBroadcast = 263,
+ SpvOpGroupIAdd = 264,
+ SpvOpGroupFAdd = 265,
+ SpvOpGroupFMin = 266,
+ SpvOpGroupUMin = 267,
+ SpvOpGroupSMin = 268,
+ SpvOpGroupFMax = 269,
+ SpvOpGroupUMax = 270,
+ SpvOpGroupSMax = 271,
+ SpvOpReadPipe = 274,
+ SpvOpWritePipe = 275,
+ SpvOpReservedReadPipe = 276,
+ SpvOpReservedWritePipe = 277,
+ SpvOpReserveReadPipePackets = 278,
+ SpvOpReserveWritePipePackets = 279,
+ SpvOpCommitReadPipe = 280,
+ SpvOpCommitWritePipe = 281,
+ SpvOpIsValidReserveId = 282,
+ SpvOpGetNumPipePackets = 283,
+ SpvOpGetMaxPipePackets = 284,
+ SpvOpGroupReserveReadPipePackets = 285,
+ SpvOpGroupReserveWritePipePackets = 286,
+ SpvOpGroupCommitReadPipe = 287,
+ SpvOpGroupCommitWritePipe = 288,
+ SpvOpEnqueueMarker = 291,
+ SpvOpEnqueueKernel = 292,
+ SpvOpGetKernelNDrangeSubGroupCount = 293,
+ SpvOpGetKernelNDrangeMaxSubGroupSize = 294,
+ SpvOpGetKernelWorkGroupSize = 295,
+ SpvOpGetKernelPreferredWorkGroupSizeMultiple = 296,
+ SpvOpRetainEvent = 297,
+ SpvOpReleaseEvent = 298,
+ SpvOpCreateUserEvent = 299,
+ SpvOpIsValidEvent = 300,
+ SpvOpSetUserEventStatus = 301,
+ SpvOpCaptureEventProfilingInfo = 302,
+ SpvOpGetDefaultQueue = 303,
+ SpvOpBuildNDRange = 304,
+ SpvOpImageSparseSampleImplicitLod = 305,
+ SpvOpImageSparseSampleExplicitLod = 306,
+ SpvOpImageSparseSampleDrefImplicitLod = 307,
+ SpvOpImageSparseSampleDrefExplicitLod = 308,
+ SpvOpImageSparseSampleProjImplicitLod = 309,
+ SpvOpImageSparseSampleProjExplicitLod = 310,
+ SpvOpImageSparseSampleProjDrefImplicitLod = 311,
+ SpvOpImageSparseSampleProjDrefExplicitLod = 312,
+ SpvOpImageSparseFetch = 313,
+ SpvOpImageSparseGather = 314,
+ SpvOpImageSparseDrefGather = 315,
+ SpvOpImageSparseTexelsResident = 316,
+ SpvOpNoLine = 317,
+ SpvOpAtomicFlagTestAndSet = 318,
+ SpvOpAtomicFlagClear = 319,
+ SpvOpImageSparseRead = 320,
+ SpvOpSizeOf = 321,
+ SpvOpTypePipeStorage = 322,
+ SpvOpConstantPipeStorage = 323,
+ SpvOpCreatePipeFromPipeStorage = 324,
+ SpvOpGetKernelLocalSizeForSubgroupCount = 325,
+ SpvOpGetKernelMaxNumSubgroups = 326,
+ SpvOpTypeNamedBarrier = 327,
+ SpvOpNamedBarrierInitialize = 328,
+ SpvOpMemoryNamedBarrier = 329,
+ SpvOpModuleProcessed = 330,
+ SpvOpExecutionModeId = 331,
+ SpvOpDecorateId = 332,
+ SpvOpGroupNonUniformElect = 333,
+ SpvOpGroupNonUniformAll = 334,
+ SpvOpGroupNonUniformAny = 335,
+ SpvOpGroupNonUniformAllEqual = 336,
+ SpvOpGroupNonUniformBroadcast = 337,
+ SpvOpGroupNonUniformBroadcastFirst = 338,
+ SpvOpGroupNonUniformBallot = 339,
+ SpvOpGroupNonUniformInverseBallot = 340,
+ SpvOpGroupNonUniformBallotBitExtract = 341,
+ SpvOpGroupNonUniformBallotBitCount = 342,
+ SpvOpGroupNonUniformBallotFindLSB = 343,
+ SpvOpGroupNonUniformBallotFindMSB = 344,
+ SpvOpGroupNonUniformShuffle = 345,
+ SpvOpGroupNonUniformShuffleXor = 346,
+ SpvOpGroupNonUniformShuffleUp = 347,
+ SpvOpGroupNonUniformShuffleDown = 348,
+ SpvOpGroupNonUniformIAdd = 349,
+ SpvOpGroupNonUniformFAdd = 350,
+ SpvOpGroupNonUniformIMul = 351,
+ SpvOpGroupNonUniformFMul = 352,
+ SpvOpGroupNonUniformSMin = 353,
+ SpvOpGroupNonUniformUMin = 354,
+ SpvOpGroupNonUniformFMin = 355,
+ SpvOpGroupNonUniformSMax = 356,
+ SpvOpGroupNonUniformUMax = 357,
+ SpvOpGroupNonUniformFMax = 358,
+ SpvOpGroupNonUniformBitwiseAnd = 359,
+ SpvOpGroupNonUniformBitwiseOr = 360,
+ SpvOpGroupNonUniformBitwiseXor = 361,
+ SpvOpGroupNonUniformLogicalAnd = 362,
+ SpvOpGroupNonUniformLogicalOr = 363,
+ SpvOpGroupNonUniformLogicalXor = 364,
+ SpvOpGroupNonUniformQuadBroadcast = 365,
+ SpvOpGroupNonUniformQuadSwap = 366,
+};
+static const int kKnownOpsCount = SpvOpGroupNonUniformQuadSwap+1;
+
+
+static const char* kSpirvOpNames[] =
+{
+ "Nop",
+ "Undef",
+ "SourceContinued",
+ "Source",
+ "SourceExtension",
+ "Name",
+ "MemberName",
+ "String",
+ "Line",
+ "#9",
+ "Extension",
+ "ExtInstImport",
+ "ExtInst",
+ "VectorShuffleCompact",
+ "MemoryModel",
+ "EntryPoint",
+ "ExecutionMode",
+ "Capability",
+ "#18",
+ "TypeVoid",
+ "TypeBool",
+ "TypeInt",
+ "TypeFloat",
+ "TypeVector",
+ "TypeMatrix",
+ "TypeImage",
+ "TypeSampler",
+ "TypeSampledImage",
+ "TypeArray",
+ "TypeRuntimeArray",
+ "TypeStruct",
+ "TypeOpaque",
+ "TypePointer",
+ "TypeFunction",
+ "TypeEvent",
+ "TypeDeviceEvent",
+ "TypeReserveId",
+ "TypeQueue",
+ "TypePipe",
+ "TypeForwardPointer",
+ "#40",
+ "ConstantTrue",
+ "ConstantFalse",
+ "Constant",
+ "ConstantComposite",
+ "ConstantSampler",
+ "ConstantNull",
+ "#47",
+ "SpecConstantTrue",
+ "SpecConstantFalse",
+ "SpecConstant",
+ "SpecConstantComposite",
+ "SpecConstantOp",
+ "#53",
+ "Function",
+ "FunctionParameter",
+ "FunctionEnd",
+ "FunctionCall",
+ "#58",
+ "Variable",
+ "ImageTexelPointer",
+ "Load",
+ "Store",
+ "CopyMemory",
+ "CopyMemorySized",
+ "AccessChain",
+ "InBoundsAccessChain",
+ "PtrAccessChain",
+ "ArrayLength",
+ "GenericPtrMemSemantics",
+ "InBoundsPtrAccessChain",
+ "Decorate",
+ "MemberDecorate",
+ "DecorationGroup",
+ "GroupDecorate",
+ "GroupMemberDecorate",
+ "#76",
+ "VectorExtractDynamic",
+ "VectorInsertDynamic",
+ "VectorShuffle",
+ "CompositeConstruct",
+ "CompositeExtract",
+ "CompositeInsert",
+ "CopyObject",
+ "Transpose",
+ "#85",
+ "SampledImage",
+ "ImageSampleImplicitLod",
+ "ImageSampleExplicitLod",
+ "ImageSampleDrefImplicitLod",
+ "ImageSampleDrefExplicitLod",
+ "ImageSampleProjImplicitLod",
+ "ImageSampleProjExplicitLod",
+ "ImageSampleProjDrefImplicitLod",
+ "ImageSampleProjDrefExplicitLod",
+ "ImageFetch",
+ "ImageGather",
+ "ImageDrefGather",
+ "ImageRead",
+ "ImageWrite",
+ "Image",
+ "ImageQueryFormat",
+ "ImageQueryOrder",
+ "ImageQuerySizeLod",
+ "ImageQuerySize",
+ "ImageQueryLod",
+ "ImageQueryLevels",
+ "ImageQuerySamples",
+ "#108",
+ "ConvertFToU",
+ "ConvertFToS",
+ "ConvertSToF",
+ "ConvertUToF",
+ "UConvert",
+ "SConvert",
+ "FConvert",
+ "QuantizeToF16",
+ "ConvertPtrToU",
+ "SatConvertSToU",
+ "SatConvertUToS",
+ "ConvertUToPtr",
+ "PtrCastToGeneric",
+ "GenericCastToPtr",
+ "GenericCastToPtrExplicit",
+ "Bitcast",
+ "#125",
+ "SNegate",
+ "FNegate",
+ "IAdd",
+ "FAdd",
+ "ISub",
+ "FSub",
+ "IMul",
+ "FMul",
+ "UDiv",
+ "SDiv",
+ "FDiv",
+ "UMod",
+ "SRem",
+ "SMod",
+ "FRem",
+ "FMod",
+ "VectorTimesScalar",
+ "MatrixTimesScalar",
+ "VectorTimesMatrix",
+ "MatrixTimesVector",
+ "MatrixTimesMatrix",
+ "OuterProduct",
+ "Dot",
+ "IAddCarry",
+ "ISubBorrow",
+ "UMulExtended",
+ "SMulExtended",
+ "#153",
+ "Any",
+ "All",
+ "IsNan",
+ "IsInf",
+ "IsFinite",
+ "IsNormal",
+ "SignBitSet",
+ "LessOrGreater",
+ "Ordered",
+ "Unordered",
+ "LogicalEqual",
+ "LogicalNotEqual",
+ "LogicalOr",
+ "LogicalAnd",
+ "LogicalNot",
+ "Select",
+ "IEqual",
+ "INotEqual",
+ "UGreaterThan",
+ "SGreaterThan",
+ "UGreaterThanEqual",
+ "SGreaterThanEqual",
+ "ULessThan",
+ "SLessThan",
+ "ULessThanEqual",
+ "SLessThanEqual",
+ "FOrdEqual",
+ "FUnordEqual",
+ "FOrdNotEqual",
+ "FUnordNotEqual",
+ "FOrdLessThan",
+ "FUnordLessThan",
+ "FOrdGreaterThan",
+ "FUnordGreaterThan",
+ "FOrdLessThanEqual",
+ "FUnordLessThanEqual",
+ "FOrdGreaterThanEqual",
+ "FUnordGreaterThanEqual",
+ "#192",
+ "#193",
+ "ShiftRightLogical",
+ "ShiftRightArithmetic",
+ "ShiftLeftLogical",
+ "BitwiseOr",
+ "BitwiseXor",
+ "BitwiseAnd",
+ "Not",
+ "BitFieldInsert",
+ "BitFieldSExtract",
+ "BitFieldUExtract",
+ "BitReverse",
+ "BitCount",
+ "#206",
+ "DPdx",
+ "DPdy",
+ "Fwidth",
+ "DPdxFine",
+ "DPdyFine",
+ "FwidthFine",
+ "DPdxCoarse",
+ "DPdyCoarse",
+ "FwidthCoarse",
+ "#216",
+ "#217",
+ "EmitVertex",
+ "EndPrimitive",
+ "EmitStreamVertex",
+ "EndStreamPrimitive",
+ "#222",
+ "#223",
+ "ControlBarrier",
+ "MemoryBarrier",
+ "#226",
+ "AtomicLoad",
+ "AtomicStore",
+ "AtomicExchange",
+ "AtomicCompareExchange",
+ "AtomicCompareExchangeWeak",
+ "AtomicIIncrement",
+ "AtomicIDecrement",
+ "AtomicIAdd",
+ "AtomicISub",
+ "AtomicSMin",
+ "AtomicUMin",
+ "AtomicSMax",
+ "AtomicUMax",
+ "AtomicAnd",
+ "AtomicOr",
+ "AtomicXor",
+ "#243",
+ "#244",
+ "Phi",
+ "LoopMerge",
+ "SelectionMerge",
+ "Label",
+ "Branch",
+ "BranchConditional",
+ "Switch",
+ "Kill",
+ "Return",
+ "ReturnValue",
+ "Unreachable",
+ "LifetimeStart",
+ "LifetimeStop",
+ "#258",
+ "GroupAsyncCopy",
+ "GroupWaitEvents",
+ "GroupAll",
+ "GroupAny",
+ "GroupBroadcast",
+ "GroupIAdd",
+ "GroupFAdd",
+ "GroupFMin",
+ "GroupUMin",
+ "GroupSMin",
+ "GroupFMax",
+ "GroupUMax",
+ "GroupSMax",
+ "#272",
+ "#273",
+ "ReadPipe",
+ "WritePipe",
+ "ReservedReadPipe",
+ "ReservedWritePipe",
+ "ReserveReadPipePackets",
+ "ReserveWritePipePackets",
+ "CommitReadPipe",
+ "CommitWritePipe",
+ "IsValidReserveId",
+ "GetNumPipePackets",
+ "GetMaxPipePackets",
+ "GroupReserveReadPipePackets",
+ "GroupReserveWritePipePackets",
+ "GroupCommitReadPipe",
+ "GroupCommitWritePipe",
+ "#289",
+ "#290",
+ "EnqueueMarker",
+ "EnqueueKernel",
+ "GetKernelNDrangeSubGroupCount",
+ "GetKernelNDrangeMaxSubGroupSize",
+ "GetKernelWorkGroupSize",
+ "GetKernelPreferredWorkGroupSizeMultiple",
+ "RetainEvent",
+ "ReleaseEvent",
+ "CreateUserEvent",
+ "IsValidEvent",
+ "SetUserEventStatus",
+ "CaptureEventProfilingInfo",
+ "GetDefaultQueue",
+ "BuildNDRange",
+ "ImageSparseSampleImplicitLod",
+ "ImageSparseSampleExplicitLod",
+ "ImageSparseSampleDrefImplicitLod",
+ "ImageSparseSampleDrefExplicitLod",
+ "ImageSparseSampleProjImplicitLod",
+ "ImageSparseSampleProjExplicitLod",
+ "ImageSparseSampleProjDrefImplicitLod",
+ "ImageSparseSampleProjDrefExplicitLod",
+ "ImageSparseFetch",
+ "ImageSparseGather",
+ "ImageSparseDrefGather",
+ "ImageSparseTexelsResident",
+ "NoLine",
+ "AtomicFlagTestAndSet",
+ "AtomicFlagClear",
+ "ImageSparseRead",
+ "SizeOf",
+ "TypePipeStorage",
+ "ConstantPipeStorage",
+ "CreatePipeFromPipeStorage",
+ "GetKernelLocalSizeForSubgroupCount",
+ "GetKernelMaxNumSubgroups",
+ "TypeNamedBarrier",
+ "NamedBarrierInitialize",
+ "MemoryNamedBarrier",
+ "ModuleProcessed",
+ "ExecutionModeId",
+ "DecorateId",
+ "GroupNonUniformElect",
+ "GroupNonUniformAll",
+ "GroupNonUniformAny",
+ "GroupNonUniformAllEqual",
+ "GroupNonUniformBroadcast",
+ "GroupNonUniformBroadcastFirst",
+ "GroupNonUniformBallot",
+ "GroupNonUniformInverseBallot",
+ "GroupNonUniformBallotBitExtract",
+ "GroupNonUniformBallotBitCount",
+ "GroupNonUniformBallotFindLSB",
+ "GroupNonUniformBallotFindMSB",
+ "GroupNonUniformShuffle",
+ "GroupNonUniformShuffleXor",
+ "GroupNonUniformShuffleUp",
+ "GroupNonUniformShuffleDown",
+ "GroupNonUniformIAdd",
+ "GroupNonUniformFAdd",
+ "GroupNonUniformIMul",
+ "GroupNonUniformFMul",
+ "GroupNonUniformSMin",
+ "GroupNonUniformUMin",
+ "GroupNonUniformFMin",
+ "GroupNonUniformSMax",
+ "GroupNonUniformUMax",
+ "GroupNonUniformFMax",
+ "GroupNonUniformBitwiseAnd",
+ "GroupNonUniformBitwiseOr",
+ "GroupNonUniformBitwiseXor",
+ "GroupNonUniformLogicalAnd",
+ "GroupNonUniformLogicalOr",
+ "GroupNonUniformLogicalXor",
+ "GroupNonUniformQuadBroadcast",
+ "GroupNonUniformQuadSwap",
+};
+static_assert(_SMOLV_ARRAY_SIZE(kSpirvOpNames) == kKnownOpsCount, "kSpirvOpNames table mismatch with known SpvOps");
+
+
+struct OpData
+{
+ uint8_t hasResult; // does it have result ID?
+ uint8_t hasType; // does it have type ID?
+ uint8_t deltaFromResult; // How many words after (optional) type+result to write out as deltas from result?
+ uint8_t varrest; // should the rest of words be written in varint encoding?
+};
+static const OpData kSpirvOpData[] =
+{
+ {0, 0, 0, 0}, // Nop
+ {1, 1, 0, 0}, // Undef
+ {0, 0, 0, 0}, // SourceContinued
+ {0, 0, 0, 1}, // Source
+ {0, 0, 0, 0}, // SourceExtension
+ {0, 0, 0, 0}, // Name
+ {0, 0, 0, 0}, // MemberName
+ {0, 0, 0, 0}, // String
+ {0, 0, 0, 1}, // Line
+ {1, 1, 0, 0}, // #9
+ {0, 0, 0, 0}, // Extension
+ {1, 0, 0, 0}, // ExtInstImport
+ {1, 1, 0, 1}, // ExtInst
+ {1, 1, 2, 1}, // VectorShuffleCompact - new in SMOLV
+ {0, 0, 0, 1}, // MemoryModel
+ {0, 0, 0, 1}, // EntryPoint
+ {0, 0, 0, 1}, // ExecutionMode
+ {0, 0, 0, 1}, // Capability
+ {1, 1, 0, 0}, // #18
+ {1, 0, 0, 1}, // TypeVoid
+ {1, 0, 0, 1}, // TypeBool
+ {1, 0, 0, 1}, // TypeInt
+ {1, 0, 0, 1}, // TypeFloat
+ {1, 0, 0, 1}, // TypeVector
+ {1, 0, 0, 1}, // TypeMatrix
+ {1, 0, 0, 1}, // TypeImage
+ {1, 0, 0, 1}, // TypeSampler
+ {1, 0, 0, 1}, // TypeSampledImage
+ {1, 0, 0, 1}, // TypeArray
+ {1, 0, 0, 1}, // TypeRuntimeArray
+ {1, 0, 0, 1}, // TypeStruct
+ {1, 0, 0, 1}, // TypeOpaque
+ {1, 0, 0, 1}, // TypePointer
+ {1, 0, 0, 1}, // TypeFunction
+ {1, 0, 0, 1}, // TypeEvent
+ {1, 0, 0, 1}, // TypeDeviceEvent
+ {1, 0, 0, 1}, // TypeReserveId
+ {1, 0, 0, 1}, // TypeQueue
+ {1, 0, 0, 1}, // TypePipe
+ {0, 0, 0, 1}, // TypeForwardPointer
+ {1, 1, 0, 0}, // #40
+ {1, 1, 0, 0}, // ConstantTrue
+ {1, 1, 0, 0}, // ConstantFalse
+ {1, 1, 0, 0}, // Constant
+ {1, 1, 9, 0}, // ConstantComposite
+ {1, 1, 0, 1}, // ConstantSampler
+ {1, 1, 0, 0}, // ConstantNull
+ {1, 1, 0, 0}, // #47
+ {1, 1, 0, 0}, // SpecConstantTrue
+ {1, 1, 0, 0}, // SpecConstantFalse
+ {1, 1, 0, 0}, // SpecConstant
+ {1, 1, 9, 0}, // SpecConstantComposite
+ {1, 1, 0, 0}, // SpecConstantOp
+ {1, 1, 0, 0}, // #53
+ {1, 1, 0, 1}, // Function
+ {1, 1, 0, 0}, // FunctionParameter
+ {0, 0, 0, 0}, // FunctionEnd
+ {1, 1, 9, 0}, // FunctionCall
+ {1, 1, 0, 0}, // #58
+ {1, 1, 0, 1}, // Variable
+ {1, 1, 0, 0}, // ImageTexelPointer
+ {1, 1, 1, 1}, // Load
+ {0, 0, 2, 1}, // Store
+ {0, 0, 0, 0}, // CopyMemory
+ {0, 0, 0, 0}, // CopyMemorySized
+ {1, 1, 0, 1}, // AccessChain
+ {1, 1, 0, 0}, // InBoundsAccessChain
+ {1, 1, 0, 0}, // PtrAccessChain
+ {1, 1, 0, 0}, // ArrayLength
+ {1, 1, 0, 0}, // GenericPtrMemSemantics
+ {1, 1, 0, 0}, // InBoundsPtrAccessChain
+ {0, 0, 0, 1}, // Decorate
+ {0, 0, 0, 1}, // MemberDecorate
+ {1, 0, 0, 0}, // DecorationGroup
+ {0, 0, 0, 0}, // GroupDecorate
+ {0, 0, 0, 0}, // GroupMemberDecorate
+ {1, 1, 0, 0}, // #76
+ {1, 1, 1, 1}, // VectorExtractDynamic
+ {1, 1, 2, 1}, // VectorInsertDynamic
+ {1, 1, 2, 1}, // VectorShuffle
+ {1, 1, 9, 0}, // CompositeConstruct
+ {1, 1, 1, 1}, // CompositeExtract
+ {1, 1, 2, 1}, // CompositeInsert
+ {1, 1, 1, 0}, // CopyObject
+ {1, 1, 0, 0}, // Transpose
+ {1, 1, 0, 0}, // #85
+ {1, 1, 0, 0}, // SampledImage
+ {1, 1, 2, 1}, // ImageSampleImplicitLod
+ {1, 1, 2, 1}, // ImageSampleExplicitLod
+ {1, 1, 3, 1}, // ImageSampleDrefImplicitLod
+ {1, 1, 3, 1}, // ImageSampleDrefExplicitLod
+ {1, 1, 2, 1}, // ImageSampleProjImplicitLod
+ {1, 1, 2, 1}, // ImageSampleProjExplicitLod
+ {1, 1, 3, 1}, // ImageSampleProjDrefImplicitLod
+ {1, 1, 3, 1}, // ImageSampleProjDrefExplicitLod
+ {1, 1, 2, 1}, // ImageFetch
+ {1, 1, 3, 1}, // ImageGather
+ {1, 1, 3, 1}, // ImageDrefGather
+ {1, 1, 2, 1}, // ImageRead
+ {0, 0, 3, 1}, // ImageWrite
+ {1, 1, 1, 0}, // Image
+ {1, 1, 1, 0}, // ImageQueryFormat
+ {1, 1, 1, 0}, // ImageQueryOrder
+ {1, 1, 2, 0}, // ImageQuerySizeLod
+ {1, 1, 1, 0}, // ImageQuerySize
+ {1, 1, 2, 0}, // ImageQueryLod
+ {1, 1, 1, 0}, // ImageQueryLevels
+ {1, 1, 1, 0}, // ImageQuerySamples
+ {1, 1, 0, 0}, // #108
+ {1, 1, 1, 0}, // ConvertFToU
+ {1, 1, 1, 0}, // ConvertFToS
+ {1, 1, 1, 0}, // ConvertSToF
+ {1, 1, 1, 0}, // ConvertUToF
+ {1, 1, 1, 0}, // UConvert
+ {1, 1, 1, 0}, // SConvert
+ {1, 1, 1, 0}, // FConvert
+ {1, 1, 1, 0}, // QuantizeToF16
+ {1, 1, 1, 0}, // ConvertPtrToU
+ {1, 1, 1, 0}, // SatConvertSToU
+ {1, 1, 1, 0}, // SatConvertUToS
+ {1, 1, 1, 0}, // ConvertUToPtr
+ {1, 1, 1, 0}, // PtrCastToGeneric
+ {1, 1, 1, 0}, // GenericCastToPtr
+ {1, 1, 1, 1}, // GenericCastToPtrExplicit
+ {1, 1, 1, 0}, // Bitcast
+ {1, 1, 0, 0}, // #125
+ {1, 1, 1, 0}, // SNegate
+ {1, 1, 1, 0}, // FNegate
+ {1, 1, 2, 0}, // IAdd
+ {1, 1, 2, 0}, // FAdd
+ {1, 1, 2, 0}, // ISub
+ {1, 1, 2, 0}, // FSub
+ {1, 1, 2, 0}, // IMul
+ {1, 1, 2, 0}, // FMul
+ {1, 1, 2, 0}, // UDiv
+ {1, 1, 2, 0}, // SDiv
+ {1, 1, 2, 0}, // FDiv
+ {1, 1, 2, 0}, // UMod
+ {1, 1, 2, 0}, // SRem
+ {1, 1, 2, 0}, // SMod
+ {1, 1, 2, 0}, // FRem
+ {1, 1, 2, 0}, // FMod
+ {1, 1, 2, 0}, // VectorTimesScalar
+ {1, 1, 2, 0}, // MatrixTimesScalar
+ {1, 1, 2, 0}, // VectorTimesMatrix
+ {1, 1, 2, 0}, // MatrixTimesVector
+ {1, 1, 2, 0}, // MatrixTimesMatrix
+ {1, 1, 2, 0}, // OuterProduct
+ {1, 1, 2, 0}, // Dot
+ {1, 1, 2, 0}, // IAddCarry
+ {1, 1, 2, 0}, // ISubBorrow
+ {1, 1, 2, 0}, // UMulExtended
+ {1, 1, 2, 0}, // SMulExtended
+ {1, 1, 0, 0}, // #153
+ {1, 1, 1, 0}, // Any
+ {1, 1, 1, 0}, // All
+ {1, 1, 1, 0}, // IsNan
+ {1, 1, 1, 0}, // IsInf
+ {1, 1, 1, 0}, // IsFinite
+ {1, 1, 1, 0}, // IsNormal
+ {1, 1, 1, 0}, // SignBitSet
+ {1, 1, 2, 0}, // LessOrGreater
+ {1, 1, 2, 0}, // Ordered
+ {1, 1, 2, 0}, // Unordered
+ {1, 1, 2, 0}, // LogicalEqual
+ {1, 1, 2, 0}, // LogicalNotEqual
+ {1, 1, 2, 0}, // LogicalOr
+ {1, 1, 2, 0}, // LogicalAnd
+ {1, 1, 1, 0}, // LogicalNot
+ {1, 1, 3, 0}, // Select
+ {1, 1, 2, 0}, // IEqual
+ {1, 1, 2, 0}, // INotEqual
+ {1, 1, 2, 0}, // UGreaterThan
+ {1, 1, 2, 0}, // SGreaterThan
+ {1, 1, 2, 0}, // UGreaterThanEqual
+ {1, 1, 2, 0}, // SGreaterThanEqual
+ {1, 1, 2, 0}, // ULessThan
+ {1, 1, 2, 0}, // SLessThan
+ {1, 1, 2, 0}, // ULessThanEqual
+ {1, 1, 2, 0}, // SLessThanEqual
+ {1, 1, 2, 0}, // FOrdEqual
+ {1, 1, 2, 0}, // FUnordEqual
+ {1, 1, 2, 0}, // FOrdNotEqual
+ {1, 1, 2, 0}, // FUnordNotEqual
+ {1, 1, 2, 0}, // FOrdLessThan
+ {1, 1, 2, 0}, // FUnordLessThan
+ {1, 1, 2, 0}, // FOrdGreaterThan
+ {1, 1, 2, 0}, // FUnordGreaterThan
+ {1, 1, 2, 0}, // FOrdLessThanEqual
+ {1, 1, 2, 0}, // FUnordLessThanEqual
+ {1, 1, 2, 0}, // FOrdGreaterThanEqual
+ {1, 1, 2, 0}, // FUnordGreaterThanEqual
+ {1, 1, 0, 0}, // #192
+ {1, 1, 0, 0}, // #193
+ {1, 1, 2, 0}, // ShiftRightLogical
+ {1, 1, 2, 0}, // ShiftRightArithmetic
+ {1, 1, 2, 0}, // ShiftLeftLogical
+ {1, 1, 2, 0}, // BitwiseOr
+ {1, 1, 2, 0}, // BitwiseXor
+ {1, 1, 2, 0}, // BitwiseAnd
+ {1, 1, 1, 0}, // Not
+ {1, 1, 4, 0}, // BitFieldInsert
+ {1, 1, 3, 0}, // BitFieldSExtract
+ {1, 1, 3, 0}, // BitFieldUExtract
+ {1, 1, 1, 0}, // BitReverse
+ {1, 1, 1, 0}, // BitCount
+ {1, 1, 0, 0}, // #206
+ {1, 1, 0, 0}, // DPdx
+ {1, 1, 0, 0}, // DPdy
+ {1, 1, 0, 0}, // Fwidth
+ {1, 1, 0, 0}, // DPdxFine
+ {1, 1, 0, 0}, // DPdyFine
+ {1, 1, 0, 0}, // FwidthFine
+ {1, 1, 0, 0}, // DPdxCoarse
+ {1, 1, 0, 0}, // DPdyCoarse
+ {1, 1, 0, 0}, // FwidthCoarse
+ {1, 1, 0, 0}, // #216
+ {1, 1, 0, 0}, // #217
+ {0, 0, 0, 0}, // EmitVertex
+ {0, 0, 0, 0}, // EndPrimitive
+ {0, 0, 0, 0}, // EmitStreamVertex
+ {0, 0, 0, 0}, // EndStreamPrimitive
+ {1, 1, 0, 0}, // #222
+ {1, 1, 0, 0}, // #223
+ {0, 0, 3, 0}, // ControlBarrier
+ {0, 0, 2, 0}, // MemoryBarrier
+ {1, 1, 0, 0}, // #226
+ {1, 1, 0, 0}, // AtomicLoad
+ {0, 0, 0, 0}, // AtomicStore
+ {1, 1, 0, 0}, // AtomicExchange
+ {1, 1, 0, 0}, // AtomicCompareExchange
+ {1, 1, 0, 0}, // AtomicCompareExchangeWeak
+ {1, 1, 0, 0}, // AtomicIIncrement
+ {1, 1, 0, 0}, // AtomicIDecrement
+ {1, 1, 0, 0}, // AtomicIAdd
+ {1, 1, 0, 0}, // AtomicISub
+ {1, 1, 0, 0}, // AtomicSMin
+ {1, 1, 0, 0}, // AtomicUMin
+ {1, 1, 0, 0}, // AtomicSMax
+ {1, 1, 0, 0}, // AtomicUMax
+ {1, 1, 0, 0}, // AtomicAnd
+ {1, 1, 0, 0}, // AtomicOr
+ {1, 1, 0, 0}, // AtomicXor
+ {1, 1, 0, 0}, // #243
+ {1, 1, 0, 0}, // #244
+ {1, 1, 0, 0}, // Phi
+ {0, 0, 2, 1}, // LoopMerge
+ {0, 0, 1, 1}, // SelectionMerge
+ {1, 0, 0, 0}, // Label
+ {0, 0, 1, 0}, // Branch
+ {0, 0, 3, 1}, // BranchConditional
+ {0, 0, 0, 0}, // Switch
+ {0, 0, 0, 0}, // Kill
+ {0, 0, 0, 0}, // Return
+ {0, 0, 0, 0}, // ReturnValue
+ {0, 0, 0, 0}, // Unreachable
+ {0, 0, 0, 0}, // LifetimeStart
+ {0, 0, 0, 0}, // LifetimeStop
+ {1, 1, 0, 0}, // #258
+ {1, 1, 0, 0}, // GroupAsyncCopy
+ {0, 0, 0, 0}, // GroupWaitEvents
+ {1, 1, 0, 0}, // GroupAll
+ {1, 1, 0, 0}, // GroupAny
+ {1, 1, 0, 0}, // GroupBroadcast
+ {1, 1, 0, 0}, // GroupIAdd
+ {1, 1, 0, 0}, // GroupFAdd
+ {1, 1, 0, 0}, // GroupFMin
+ {1, 1, 0, 0}, // GroupUMin
+ {1, 1, 0, 0}, // GroupSMin
+ {1, 1, 0, 0}, // GroupFMax
+ {1, 1, 0, 0}, // GroupUMax
+ {1, 1, 0, 0}, // GroupSMax
+ {1, 1, 0, 0}, // #272
+ {1, 1, 0, 0}, // #273
+ {1, 1, 0, 0}, // ReadPipe
+ {1, 1, 0, 0}, // WritePipe
+ {1, 1, 0, 0}, // ReservedReadPipe
+ {1, 1, 0, 0}, // ReservedWritePipe
+ {1, 1, 0, 0}, // ReserveReadPipePackets
+ {1, 1, 0, 0}, // ReserveWritePipePackets
+ {0, 0, 0, 0}, // CommitReadPipe
+ {0, 0, 0, 0}, // CommitWritePipe
+ {1, 1, 0, 0}, // IsValidReserveId
+ {1, 1, 0, 0}, // GetNumPipePackets
+ {1, 1, 0, 0}, // GetMaxPipePackets
+ {1, 1, 0, 0}, // GroupReserveReadPipePackets
+ {1, 1, 0, 0}, // GroupReserveWritePipePackets
+ {0, 0, 0, 0}, // GroupCommitReadPipe
+ {0, 0, 0, 0}, // GroupCommitWritePipe
+ {1, 1, 0, 0}, // #289
+ {1, 1, 0, 0}, // #290
+ {1, 1, 0, 0}, // EnqueueMarker
+ {1, 1, 0, 0}, // EnqueueKernel
+ {1, 1, 0, 0}, // GetKernelNDrangeSubGroupCount
+ {1, 1, 0, 0}, // GetKernelNDrangeMaxSubGroupSize
+ {1, 1, 0, 0}, // GetKernelWorkGroupSize
+ {1, 1, 0, 0}, // GetKernelPreferredWorkGroupSizeMultiple
+ {0, 0, 0, 0}, // RetainEvent
+ {0, 0, 0, 0}, // ReleaseEvent
+ {1, 1, 0, 0}, // CreateUserEvent
+ {1, 1, 0, 0}, // IsValidEvent
+ {0, 0, 0, 0}, // SetUserEventStatus
+ {0, 0, 0, 0}, // CaptureEventProfilingInfo
+ {1, 1, 0, 0}, // GetDefaultQueue
+ {1, 1, 0, 0}, // BuildNDRange
+ {1, 1, 2, 1}, // ImageSparseSampleImplicitLod
+ {1, 1, 2, 1}, // ImageSparseSampleExplicitLod
+ {1, 1, 3, 1}, // ImageSparseSampleDrefImplicitLod
+ {1, 1, 3, 1}, // ImageSparseSampleDrefExplicitLod
+ {1, 1, 2, 1}, // ImageSparseSampleProjImplicitLod
+ {1, 1, 2, 1}, // ImageSparseSampleProjExplicitLod
+ {1, 1, 3, 1}, // ImageSparseSampleProjDrefImplicitLod
+ {1, 1, 3, 1}, // ImageSparseSampleProjDrefExplicitLod
+ {1, 1, 2, 1}, // ImageSparseFetch
+ {1, 1, 3, 1}, // ImageSparseGather
+ {1, 1, 3, 1}, // ImageSparseDrefGather
+ {1, 1, 1, 0}, // ImageSparseTexelsResident
+ {0, 0, 0, 0}, // NoLine
+ {1, 1, 0, 0}, // AtomicFlagTestAndSet
+ {0, 0, 0, 0}, // AtomicFlagClear
+ {1, 1, 0, 0}, // ImageSparseRead
+ {1, 1, 0, 0}, // SizeOf
+ {1, 1, 0, 0}, // TypePipeStorage
+ {1, 1, 0, 0}, // ConstantPipeStorage
+ {1, 1, 0, 0}, // CreatePipeFromPipeStorage
+ {1, 1, 0, 0}, // GetKernelLocalSizeForSubgroupCount
+ {1, 1, 0, 0}, // GetKernelMaxNumSubgroups
+ {1, 1, 0, 0}, // TypeNamedBarrier
+ {1, 1, 0, 1}, // NamedBarrierInitialize
+ {0, 0, 2, 1}, // MemoryNamedBarrier
+ {1, 1, 0, 0}, // ModuleProcessed
+ {0, 0, 0, 1}, // ExecutionModeId
+ {0, 0, 0, 1}, // DecorateId
+ {1, 1, 1, 1}, // GroupNonUniformElect
+ {1, 1, 1, 1}, // GroupNonUniformAll
+ {1, 1, 1, 1}, // GroupNonUniformAny
+ {1, 1, 1, 1}, // GroupNonUniformAllEqual
+ {1, 1, 1, 1}, // GroupNonUniformBroadcast
+ {1, 1, 1, 1}, // GroupNonUniformBroadcastFirst
+ {1, 1, 1, 1}, // GroupNonUniformBallot
+ {1, 1, 1, 1}, // GroupNonUniformInverseBallot
+ {1, 1, 1, 1}, // GroupNonUniformBallotBitExtract
+ {1, 1, 1, 1}, // GroupNonUniformBallotBitCount
+ {1, 1, 1, 1}, // GroupNonUniformBallotFindLSB
+ {1, 1, 1, 1}, // GroupNonUniformBallotFindMSB
+ {1, 1, 1, 1}, // GroupNonUniformShuffle
+ {1, 1, 1, 1}, // GroupNonUniformShuffleXor
+ {1, 1, 1, 1}, // GroupNonUniformShuffleUp
+ {1, 1, 1, 1}, // GroupNonUniformShuffleDown
+ {1, 1, 1, 1}, // GroupNonUniformIAdd
+ {1, 1, 1, 1}, // GroupNonUniformFAdd
+ {1, 1, 1, 1}, // GroupNonUniformIMul
+ {1, 1, 1, 1}, // GroupNonUniformFMul
+ {1, 1, 1, 1}, // GroupNonUniformSMin
+ {1, 1, 1, 1}, // GroupNonUniformUMin
+ {1, 1, 1, 1}, // GroupNonUniformFMin
+ {1, 1, 1, 1}, // GroupNonUniformSMax
+ {1, 1, 1, 1}, // GroupNonUniformUMax
+ {1, 1, 1, 1}, // GroupNonUniformFMax
+ {1, 1, 1, 1}, // GroupNonUniformBitwiseAnd
+ {1, 1, 1, 1}, // GroupNonUniformBitwiseOr
+ {1, 1, 1, 1}, // GroupNonUniformBitwiseXor
+ {1, 1, 1, 1}, // GroupNonUniformLogicalAnd
+ {1, 1, 1, 1}, // GroupNonUniformLogicalOr
+ {1, 1, 1, 1}, // GroupNonUniformLogicalXor
+ {1, 1, 1, 1}, // GroupNonUniformQuadBroadcast
+ {1, 1, 1, 1}, // GroupNonUniformQuadSwap
+};
+static_assert(_SMOLV_ARRAY_SIZE(kSpirvOpData) == kKnownOpsCount, "kSpirvOpData table mismatch with known SpvOps");
+
+// Instruction encoding depends on the table that describes the various SPIR-V opcodes.
+// Whenever we change or expand the table, we need to bump up the SMOL-V version, and make
+// sure that we can still decode files encoded by an older version.
+static int smolv_GetKnownOpsCount(int version)
+{
+ if (version == 0)
+ return SpvOpModuleProcessed+1;
+ if (version == 1) // 2020 February, version 1 added ExecutionModeId..GroupNonUniformQuadSwap
+ return SpvOpGroupNonUniformQuadSwap+1;
+ return 0;
+}
+
+static bool smolv_OpHasResult(SpvOp op, int opsCount)
+{
+ if (op < 0 || op >= opsCount)
+ return false;
+ return kSpirvOpData[op].hasResult != 0;
+}
+
+static bool smolv_OpHasType(SpvOp op, int opsCount)
+{
+ if (op < 0 || op >= opsCount)
+ return false;
+ return kSpirvOpData[op].hasType != 0;
+}
+
+static int smolv_OpDeltaFromResult(SpvOp op, int opsCount)
+{
+ if (op < 0 || op >= opsCount)
+ return 0;
+ return kSpirvOpData[op].deltaFromResult;
+}
+
+static bool smolv_OpVarRest(SpvOp op, int opsCount)
+{
+ if (op < 0 || op >= opsCount)
+ return false;
+ return kSpirvOpData[op].varrest != 0;
+}
+
+static bool smolv_OpDebugInfo(SpvOp op, int opsCount)
+{
+ return
+ op == SpvOpSourceContinued ||
+ op == SpvOpSource ||
+ op == SpvOpSourceExtension ||
+ op == SpvOpName ||
+ op == SpvOpMemberName ||
+ op == SpvOpString ||
+ op == SpvOpLine ||
+ op == SpvOpNoLine ||
+ op == SpvOpModuleProcessed;
+}
+
+
+static int smolv_DecorationExtraOps(int dec)
+{
+ if (dec == 0 || (dec >= 2 && dec <= 5)) // RelaxedPrecision, Block..ColMajor
+ return 0;
+ if (dec >= 29 && dec <= 37) // Stream..XfbStride
+ return 1;
+ return -1; // unknown, encode length
+}
+
+
+// --------------------------------------------------------------------------------------------
+
+
+static bool smolv_CheckGenericHeader(const uint32_t* words, size_t wordCount, uint32_t expectedMagic, uint32_t versionMask)
+{
+ if (!words)
+ return false;
+ if (wordCount < 5)
+ return false;
+
+ uint32_t headerMagic = words[0];
+ if (headerMagic != expectedMagic)
+ return false;
+ uint32_t headerVersion = words[1] & versionMask;
+ if (headerVersion < 0x00010000 || headerVersion > 0x00010500)
+ return false; // only support 1.0 through 1.5
+
+ return true;
+}
+
+static const int kSpirVHeaderMagic = 0x07230203;
+static const int kSmolHeaderMagic = 0x534D4F4C; // "SMOL"
+
+static const int kSmolCurrEncodingVersion = 1;
+
+static bool smolv_CheckSpirVHeader(const uint32_t* words, size_t wordCount)
+{
+ //@TODO: if SPIR-V header magic was reversed, that means the file got written
+ // in a "big endian" order. Need to byteswap all words then.
+ return smolv_CheckGenericHeader(words, wordCount, kSpirVHeaderMagic, 0xFFFFFFFF);
+}
+static bool smolv_CheckSmolHeader(const uint8_t* bytes, size_t byteCount)
+{
+ if (!smolv_CheckGenericHeader((const uint32_t*)bytes, byteCount/4, kSmolHeaderMagic, 0x00FFFFFF))
+ return false;
+ if (byteCount < 24) // one more word past header to store decoded length
+ return false;
+ // SMOL-V version
+ int smolVersion = ((const uint32_t*)bytes)[1] >> 24;
+ if (smolVersion < 0 || smolVersion > kSmolCurrEncodingVersion)
+ return false;
+ return true;
+}
+
+
+static void smolv_Write4(smolv::ByteArray& arr, uint32_t v)
+{
+ arr.push_back(v & 0xFF);
+ arr.push_back((v >> 8) & 0xFF);
+ arr.push_back((v >> 16) & 0xFF);
+ arr.push_back(v >> 24);
+}
+
+static void smolv_Write4(uint8_t*& buf, uint32_t v)
+{
+ memcpy(buf, &v, 4);
+ buf += 4;
+}
+
+
+static bool smolv_Read4(const uint8_t*& data, const uint8_t* dataEnd, uint32_t& outv)
+{
+ if (data + 4 > dataEnd)
+ return false;
+ outv = (data[0]) | (data[1] << 8) | (data[2] << 16) | (data[3] << 24);
+ data += 4;
+ return true;
+}
+
+
+// --------------------------------------------------------------------------------------------
+
+// Variable-length integer encoding for unsigned integers. In each byte:
+// - highest bit set if more bytes follow, cleared if this is last byte.
+// - other 7 bits are the actual value payload.
+// Takes 1-5 bytes to encode an integer (values between 0 and 127 take one byte, etc.).
+
+static void smolv_WriteVarint(smolv::ByteArray& arr, uint32_t v)
+{
+ while (v > 127)
+ {
+ arr.push_back((v & 127) | 128);
+ v >>= 7;
+ }
+ arr.push_back(v & 127);
+}
+
+static bool smolv_ReadVarint(const uint8_t*& data, const uint8_t* dataEnd, uint32_t& outVal)
+{
+ uint32_t v = 0;
+ uint32_t shift = 0;
+ while (data < dataEnd)
+ {
+ uint8_t b = *data;
+ v |= (b & 127) << shift;
+ shift += 7;
+ data++;
+ if (!(b & 128))
+ break;
+ }
+ outVal = v;
+ return true; //@TODO: report failures
+}
+
+static uint32_t smolv_ZigEncode(int32_t i)
+{
+ return (uint32_t(i) << 1) ^ (i >> 31);
+}
+
+static int32_t smolv_ZigDecode(uint32_t u)
+{
+ return (u & 1) ? ((u >> 1) ^ ~0) : (u >> 1);
+}
+
+
+// Remap most common Op codes (Load, Store, Decorate, VectorShuffle etc.) to be in < 16 range, for
+// more compact varint encoding. This basically swaps rarely used op values that are < 16 with the
+// ones that are common.
+
+static SpvOp smolv_RemapOp(SpvOp op)
+{
+# define _SMOLV_SWAP_OP(op1,op2) if (op==op1) return op2; if (op==op2) return op1
+ _SMOLV_SWAP_OP(SpvOpDecorate,SpvOpNop); // 0: 24%
+ _SMOLV_SWAP_OP(SpvOpLoad,SpvOpUndef); // 1: 17%
+ _SMOLV_SWAP_OP(SpvOpStore,SpvOpSourceContinued); // 2: 9%
+ _SMOLV_SWAP_OP(SpvOpAccessChain,SpvOpSource); // 3: 7.2%
+ _SMOLV_SWAP_OP(SpvOpVectorShuffle,SpvOpSourceExtension); // 4: 5.0%
+ // Name - already small enum value - 5: 4.4%
+ // MemberName - already small enum value - 6: 2.9%
+ _SMOLV_SWAP_OP(SpvOpMemberDecorate,SpvOpString); // 7: 4.0%
+ _SMOLV_SWAP_OP(SpvOpLabel,SpvOpLine); // 8: 0.9%
+ _SMOLV_SWAP_OP(SpvOpVariable,(SpvOp)9); // 9: 3.9%
+ _SMOLV_SWAP_OP(SpvOpFMul,SpvOpExtension); // 10: 3.9%
+ _SMOLV_SWAP_OP(SpvOpFAdd,SpvOpExtInstImport); // 11: 2.5%
+ // ExtInst - already small enum value - 12: 1.2%
+ // VectorShuffleCompact - already small enum value - used for compact shuffle encoding
+ _SMOLV_SWAP_OP(SpvOpTypePointer,SpvOpMemoryModel); // 14: 2.2%
+ _SMOLV_SWAP_OP(SpvOpFNegate,SpvOpEntryPoint); // 15: 1.1%
+# undef _SMOLV_SWAP_OP
+ return op;
+}
+
+
+// For most compact varint encoding of common instructions, the instruction length should come out
+// into 3 bits (be <8). SPIR-V instruction lengths are always at least 1, and for some other
+// instructions they are guaranteed to be some other minimum length. Adjust the length before encoding,
+// and after decoding accordingly.
+
+static uint32_t smolv_EncodeLen(SpvOp op, uint32_t len)
+{
+ len--;
+ if (op == SpvOpVectorShuffle) len -= 4;
+ if (op == SpvOpVectorShuffleCompact) len -= 4;
+ if (op == SpvOpDecorate) len -= 2;
+ if (op == SpvOpLoad) len -= 3;
+ if (op == SpvOpAccessChain) len -= 3;
+ return len;
+}
+
+static uint32_t smolv_DecodeLen(SpvOp op, uint32_t len)
+{
+ len++;
+ if (op == SpvOpVectorShuffle) len += 4;
+ if (op == SpvOpVectorShuffleCompact) len += 4;
+ if (op == SpvOpDecorate) len += 2;
+ if (op == SpvOpLoad) len += 3;
+ if (op == SpvOpAccessChain) len += 3;
+ return len;
+}
+
+
+// Shuffling bits of length + opcode to be more compact in varint encoding in typical cases:
+// 0x LLLL OOOO is how SPIR-V encodes it (L=length, O=op), we shuffle into:
+// 0x LLLO OOLO, so that common case (op<16, len<8) is encoded into one byte.
+
+static bool smolv_WriteLengthOp(smolv::ByteArray& arr, uint32_t len, SpvOp op)
+{
+ len = smolv_EncodeLen(op, len);
+ // SPIR-V length field is 16 bits; if we get a larger value that means something
+ // was wrong, e.g. a vector shuffle instruction with less than 4 words (and our
+ // adjustment to common lengths in smolv_EncodeLen wrapped around)
+ if (len > 0xFFFF)
+ return false;
+ op = smolv_RemapOp(op);
+ uint32_t oplen = ((len >> 4) << 20) | ((op >> 4) << 8) | ((len & 0xF) << 4) | (op & 0xF);
+ smolv_WriteVarint(arr, oplen);
+ return true;
+}
+
+static bool smolv_ReadLengthOp(const uint8_t*& data, const uint8_t* dataEnd, uint32_t& outLen, SpvOp& outOp)
+{
+ uint32_t val;
+ if (!smolv_ReadVarint(data, dataEnd, val))
+ return false;
+ outLen = ((val >> 20) << 4) | ((val >> 4) & 0xF);
+ outOp = (SpvOp)(((val >> 4) & 0xFFF0) | (val & 0xF));
+
+ outOp = smolv_RemapOp(outOp);
+ outLen = smolv_DecodeLen(outOp, outLen);
+ return true;
+}
+
+
+
+#define _SMOLV_READ_OP(len, words, op) \
+ uint32_t len = words[0] >> 16; \
+ if (len < 1) return false; /* malformed instruction, length needs to be at least 1 */ \
+ if (words + len > wordsEnd) return false; /* malformed instruction, goes past end of data */ \
+ SpvOp op = (SpvOp)(words[0] & 0xFFFF)
+
+
+bool smolv::Encode(const void* spirvData, size_t spirvSize, ByteArray& outSmolv, uint32_t flags, StripOpNameFilterFunc stripFilter)
+{
+ const size_t wordCount = spirvSize / 4;
+ if (wordCount * 4 != spirvSize)
+ return false;
+ const uint32_t* words = (const uint32_t*)spirvData;
+ const uint32_t* wordsEnd = words + wordCount;
+ if (!smolv_CheckSpirVHeader(words, wordCount))
+ return false;
+
+ // reserve space in output (typical compression is to about 30%; reserve half of input space)
+ outSmolv.reserve(outSmolv.size() + spirvSize/2);
+
+ // header (matches SPIR-V one, except different magic)
+ smolv_Write4(outSmolv, kSmolHeaderMagic);
+ smolv_Write4(outSmolv, (words[1] & 0x00FFFFFF) + (kSmolCurrEncodingVersion<<24)); // SPIR-V version (_XXX) + SMOL-V version (X___)
+ smolv_Write4(outSmolv, words[2]); // generator
+ smolv_Write4(outSmolv, words[3]); // bound
+ smolv_Write4(outSmolv, words[4]); // schema
+
+ const size_t headerSpirvSizeOffset = outSmolv.size(); // size field may get updated later if stripping is enabled
+ smolv_Write4(outSmolv, (uint32_t)spirvSize); // space needed to decode (i.e. original SPIR-V size)
+
+ size_t strippedSpirvWordCount = wordCount;
+ uint32_t prevResult = 0;
+ uint32_t prevDecorate = 0;
+
+ const int knownOpsCount = smolv_GetKnownOpsCount(kSmolCurrEncodingVersion);
+
+ words += 5;
+ while (words < wordsEnd)
+ {
+ _SMOLV_READ_OP(instrLen, words, op);
+
+ if ((flags & kEncodeFlagStripDebugInfo) && smolv_OpDebugInfo(op, knownOpsCount))
+ {
+ if (!stripFilter || op != SpvOpName || !stripFilter(reinterpret_cast<const char*>(&words[2])))
+ {
+ strippedSpirvWordCount -= instrLen;
+ words += instrLen;
+ continue;
+ }
+ }
+
+ // A usual case of vector shuffle, with less than 4 components, each with a value
+ // in [0..3] range: encode it in a more compact form, with the swizzle pattern in one byte.
+ // Turn this into a VectorShuffleCompact instruction, that takes up unused slot in Ops.
+ uint32_t swizzle = 0;
+ if (op == SpvOpVectorShuffle && instrLen <= 9)
+ {
+ uint32_t swz0 = instrLen > 5 ? words[5] : 0;
+ uint32_t swz1 = instrLen > 6 ? words[6] : 0;
+ uint32_t swz2 = instrLen > 7 ? words[7] : 0;
+ uint32_t swz3 = instrLen > 8 ? words[8] : 0;
+ if (swz0 < 4 && swz1 < 4 && swz2 < 4 && swz3 < 4)
+ {
+ op = SpvOpVectorShuffleCompact;
+ swizzle = (swz0 << 6) | (swz1 << 4) | (swz2 << 2) | (swz3);
+ }
+ }
+
+ // length + opcode
+ if (!smolv_WriteLengthOp(outSmolv, instrLen, op))
+ return false;
+
+ size_t ioffs = 1;
+ // write type as varint, if we have it
+ if (smolv_OpHasType(op, knownOpsCount))
+ {
+ if (ioffs >= instrLen)
+ return false;
+ smolv_WriteVarint(outSmolv, words[ioffs]);
+ ioffs++;
+ }
+ // write result as delta+zig+varint, if we have it
+ if (smolv_OpHasResult(op, knownOpsCount))
+ {
+ if (ioffs >= instrLen)
+ return false;
+ uint32_t v = words[ioffs];
+ smolv_WriteVarint(outSmolv, smolv_ZigEncode(v - prevResult)); // some deltas are negative, use zig
+ prevResult = v;
+ ioffs++;
+ }
+
+ // Decorate & MemberDecorate: IDs relative to previous decorate
+ if (op == SpvOpDecorate || op == SpvOpMemberDecorate)
+ {
+ if (ioffs >= instrLen)
+ return false;
+ uint32_t v = words[ioffs];
+ smolv_WriteVarint(outSmolv, smolv_ZigEncode(v - prevDecorate)); // spirv-remapped deltas often negative, use zig
+ prevDecorate = v;
+ ioffs++;
+ }
+
+ // MemberDecorate special encoding: whole row of MemberDecorate instructions is often referring
+ // to the same type and linearly increasing member indices. Scan ahead to see how many we have,
+ // and encode whole bunch as one.
+ if (op == SpvOpMemberDecorate)
+ {
+ // scan ahead until we reach end, non-member-decoration or different type
+ const uint32_t decorationType = words[ioffs-1];
+ const uint32_t* memberWords = words;
+ uint32_t prevIndex = 0;
+ uint32_t prevOffset = 0;
+ // write a byte on how many we have encoded as a bunch
+ size_t countLocation = outSmolv.size();
+ outSmolv.push_back(0);
+ int count = 0;
+ while (memberWords < wordsEnd && count < 255)
+ {
+ _SMOLV_READ_OP(memberLen, memberWords, memberOp);
+ if (memberOp != SpvOpMemberDecorate)
+ break;
+ if (memberLen < 4)
+ return false; // invalid input
+ if (memberWords[1] != decorationType)
+ break;
+
+ // write member index as delta from previous
+ uint32_t memberIndex = memberWords[2];
+ smolv_WriteVarint(outSmolv, memberIndex - prevIndex);
+ prevIndex = memberIndex;
+
+ // decoration (and length if not common/known)
+ uint32_t memberDec = memberWords[3];
+ smolv_WriteVarint(outSmolv, memberDec);
+ const int knownExtraOps = smolv_DecorationExtraOps(memberDec);
+ if (knownExtraOps == -1)
+ smolv_WriteVarint(outSmolv, memberLen-4);
+ else if (unsigned(knownExtraOps) + 4 != memberLen)
+ return false; // invalid input
+
+ // Offset decorations are most often linearly increasing, so encode as deltas
+ if (memberDec == 35) // Offset
+ {
+ if (memberLen != 5)
+ return false;
+ smolv_WriteVarint(outSmolv, memberWords[4]-prevOffset);
+ prevOffset = memberWords[4];
+ }
+ else
+ {
+ // write rest of decorations as varint
+ for (uint32_t i = 4; i < memberLen; ++i)
+ smolv_WriteVarint(outSmolv, memberWords[i]);
+ }
+
+ memberWords += memberLen;
+ ++count;
+ }
+ outSmolv[countLocation] = uint8_t(count);
+ words = memberWords;
+ continue;
+ }
+
+ // Write out this many IDs, encoding them relative+zigzag to result ID
+ int relativeCount = smolv_OpDeltaFromResult(op, knownOpsCount);
+ for (int i = 0; i < relativeCount && ioffs < instrLen; ++i, ++ioffs)
+ {
+ if (ioffs >= instrLen)
+ return false;
+ uint32_t delta = prevResult - words[ioffs];
+ // some deltas are negative (often on branches, or if program was processed by spirv-remap),
+ // so use zig encoding
+ smolv_WriteVarint(outSmolv, smolv_ZigEncode(delta));
+ }
+
+ if (op == SpvOpVectorShuffleCompact)
+ {
+ // compact vector shuffle, just write out single swizzle byte
+ outSmolv.push_back(uint8_t(swizzle));
+ ioffs = instrLen;
+ }
+ else if (smolv_OpVarRest(op, knownOpsCount))
+ {
+ // write out rest of words with variable encoding (expected to be small integers)
+ for (; ioffs < instrLen; ++ioffs)
+ smolv_WriteVarint(outSmolv, words[ioffs]);
+ }
+ else
+ {
+ // write out rest of words without any encoding
+ for (; ioffs < instrLen; ++ioffs)
+ smolv_Write4(outSmolv, words[ioffs]);
+ }
+
+ words += instrLen;
+ }
+
+ if (strippedSpirvWordCount != wordCount)
+ {
+ uint8_t* headerSpirvSize = &outSmolv[headerSpirvSizeOffset];
+ smolv_Write4(headerSpirvSize, (uint32_t)strippedSpirvWordCount * 4);
+ }
+
+ return true;
+}
+
+
+size_t smolv::GetDecodedBufferSize(const void* smolvData, size_t smolvSize)
+{
+ if (!smolv_CheckSmolHeader((const uint8_t*)smolvData, smolvSize))
+ return 0;
+ const uint32_t* words = (const uint32_t*)smolvData;
+ return words[5];
+}
+
+
+bool smolv::Decode(const void* smolvData, size_t smolvSize, void* spirvOutputBuffer, size_t spirvOutputBufferSize, uint32_t flags)
+{
+ // check header, and whether we have enough output buffer space
+ const size_t neededBufferSize = GetDecodedBufferSize(smolvData, smolvSize);
+ if (neededBufferSize == 0)
+ return false; // invalid SMOL-V
+ if (spirvOutputBufferSize < neededBufferSize)
+ return false; // not enough space in output buffer
+ if (spirvOutputBuffer == NULL)
+ return false; // output buffer is null
+
+ const uint8_t* bytes = (const uint8_t*)smolvData;
+ const uint8_t* bytesEnd = bytes + smolvSize;
+
+ uint8_t* outSpirv = (uint8_t*)spirvOutputBuffer;
+
+ uint32_t val;
+ int smolVersion = 0;
+
+ // header
+ smolv_Write4(outSpirv, kSpirVHeaderMagic); bytes += 4;
+ smolv_Read4(bytes, bytesEnd, val); smolVersion = val >> 24; val &= 0x00FFFFFF; smolv_Write4(outSpirv, val); // version
+ smolv_Read4(bytes, bytesEnd, val); smolv_Write4(outSpirv, val); // generator
+ smolv_Read4(bytes, bytesEnd, val); smolv_Write4(outSpirv, val); // bound
+ smolv_Read4(bytes, bytesEnd, val); smolv_Write4(outSpirv, val); // schema
+ bytes += 4; // decode buffer size
+
+ // there are two SMOL-V encoding versions, both not indicating anything in their header version field:
+ // one that is called "before zero" here (2016-08-31 code). Support decoding that one only by presence
+ // of this special flag.
+ const bool beforeZeroVersion = smolVersion == 0 && (flags & kDecodeFlagUse20160831AsZeroVersion) != 0;
+
+ const int knownOpsCount = smolv_GetKnownOpsCount(smolVersion);
+
+ uint32_t prevResult = 0;
+ uint32_t prevDecorate = 0;
+
+ while (bytes < bytesEnd)
+ {
+ // read length + opcode
+ uint32_t instrLen;
+ SpvOp op;
+ if (!smolv_ReadLengthOp(bytes, bytesEnd, instrLen, op))
+ return false;
+ const bool wasSwizzle = (op == SpvOpVectorShuffleCompact);
+ if (wasSwizzle)
+ op = SpvOpVectorShuffle;
+ smolv_Write4(outSpirv, (instrLen << 16) | op);
+
+ size_t ioffs = 1;
+
+ // read type as varint, if we have it
+ if (smolv_OpHasType(op, knownOpsCount))
+ {
+ if (!smolv_ReadVarint(bytes, bytesEnd, val)) return false;
+ smolv_Write4(outSpirv, val);
+ ioffs++;
+ }
+ // read result as delta+varint, if we have it
+ if (smolv_OpHasResult(op, knownOpsCount))
+ {
+ if (!smolv_ReadVarint(bytes, bytesEnd, val)) return false;
+ val = prevResult + smolv_ZigDecode(val);
+ smolv_Write4(outSpirv, val);
+ prevResult = val;
+ ioffs++;
+ }
+
+ // Decorate: IDs relative to previous decorate
+ if (op == SpvOpDecorate || op == SpvOpMemberDecorate)
+ {
+ if (!smolv_ReadVarint(bytes, bytesEnd, val)) return false;
+ // "before zero" version did not use zig encoding for the value
+ val = prevDecorate + (beforeZeroVersion ? val : smolv_ZigDecode(val));
+ smolv_Write4(outSpirv, val);
+ prevDecorate = val;
+ ioffs++;
+ }
+
+ // MemberDecorate special decoding
+ if (op == SpvOpMemberDecorate && !beforeZeroVersion)
+ {
+ if (bytes >= bytesEnd)
+ return false; // broken input
+ int count = *bytes++;
+ int prevIndex = 0;
+ int prevOffset = 0;
+ for (int m = 0; m < count; ++m)
+ {
+ // read member index
+ uint32_t memberIndex;
+ if (!smolv_ReadVarint(bytes, bytesEnd, memberIndex)) return false;
+ memberIndex += prevIndex;
+ prevIndex = memberIndex;
+
+ // decoration (and length if not common/known)
+ uint32_t memberDec;
+ if (!smolv_ReadVarint(bytes, bytesEnd, memberDec)) return false;
+ const int knownExtraOps = smolv_DecorationExtraOps(memberDec);
+ uint32_t memberLen;
+ if (knownExtraOps == -1)
+ {
+ if (!smolv_ReadVarint(bytes, bytesEnd, memberLen)) return false;
+ memberLen += 4;
+ }
+ else
+ memberLen = 4 + knownExtraOps;
+
+ // write SPIR-V op+length (unless it's first member decoration, in which case it was written before)
+ if (m != 0)
+ {
+ smolv_Write4(outSpirv, (memberLen << 16) | op);
+ smolv_Write4(outSpirv, prevDecorate);
+ }
+ smolv_Write4(outSpirv, memberIndex);
+ smolv_Write4(outSpirv, memberDec);
+ // Special case for Offset decorations
+ if (memberDec == 35) // Offset
+ {
+ if (memberLen != 5)
+ return false;
+ if (!smolv_ReadVarint(bytes, bytesEnd, val)) return false;
+ val += prevOffset;
+ smolv_Write4(outSpirv, val);
+ prevOffset = val;
+ }
+ else
+ {
+ for (uint32_t i = 4; i < memberLen; ++i)
+ {
+ if (!smolv_ReadVarint(bytes, bytesEnd, val)) return false;
+ smolv_Write4(outSpirv, val);
+ }
+ }
+ }
+ continue;
+ }
+
+ // Read this many IDs, that are relative to result ID
+ int relativeCount = smolv_OpDeltaFromResult(op, knownOpsCount);
+ // "before zero" version only used zig encoding for IDs of several ops; after
+ // that ops got zig encoding for their IDs
+ bool zigDecodeVals = true;
+ if (beforeZeroVersion)
+ {
+ if (op != SpvOpControlBarrier && op != SpvOpMemoryBarrier && op != SpvOpLoopMerge && op != SpvOpSelectionMerge && op != SpvOpBranch && op != SpvOpBranchConditional && op != SpvOpMemoryNamedBarrier)
+ zigDecodeVals = false;
+ }
+ for (int i = 0; i < relativeCount && ioffs < instrLen; ++i, ++ioffs)
+ {
+ if (!smolv_ReadVarint(bytes, bytesEnd, val)) return false;
+ if (zigDecodeVals)
+ val = smolv_ZigDecode(val);
+ smolv_Write4(outSpirv, prevResult - val);
+ }
+
+ if (wasSwizzle && instrLen <= 9)
+ {
+ uint32_t swizzle = *bytes++;
+ if (instrLen > 5) smolv_Write4(outSpirv, (swizzle >> 6) & 3);
+ if (instrLen > 6) smolv_Write4(outSpirv, (swizzle >> 4) & 3);
+ if (instrLen > 7) smolv_Write4(outSpirv, (swizzle >> 2) & 3);
+ if (instrLen > 8) smolv_Write4(outSpirv, swizzle & 3);
+ }
+ else if (smolv_OpVarRest(op, knownOpsCount))
+ {
+ // read rest of words with variable encoding
+ for (; ioffs < instrLen; ++ioffs)
+ {
+ if (!smolv_ReadVarint(bytes, bytesEnd, val)) return false;
+ smolv_Write4(outSpirv, val);
+ }
+ }
+ else
+ {
+ // read rest of words without any encoding
+ for (; ioffs < instrLen; ++ioffs)
+ {
+ if (!smolv_Read4(bytes, bytesEnd, val)) return false;
+ smolv_Write4(outSpirv, val);
+ }
+ }
+ }
+
+ if ((uint8_t*)spirvOutputBuffer + neededBufferSize != outSpirv)
+ return false; // something went wrong during decoding? we should have decoded to exact output size
+
+ return true;
+}
+
+
+
+// --------------------------------------------------------------------------------------------
+// Calculating instruction count / space stats on SPIR-V and SMOL-V
+
+
+struct smolv::Stats
+{
+ Stats() { memset(this, 0, sizeof(*this)); }
+ size_t opCounts[kKnownOpsCount];
+ size_t opSizes[kKnownOpsCount];
+ size_t smolOpSizes[kKnownOpsCount];
+ size_t varintCountsOp[6];
+ size_t varintCountsType[6];
+ size_t varintCountsRes[6];
+ size_t varintCountsOther[6];
+ size_t totalOps;
+ size_t totalSize;
+ size_t totalSizeSmol;
+ size_t inputCount;
+};
+
+
+smolv::Stats* smolv::StatsCreate()
+{
+ return new Stats();
+}
+
+void smolv::StatsDelete(smolv::Stats *s)
+{
+ delete s;
+}
+
+
+bool smolv::StatsCalculate(smolv::Stats* stats, const void* spirvData, size_t spirvSize)
+{
+ if (!stats)
+ return false;
+
+ const size_t wordCount = spirvSize / 4;
+ if (wordCount * 4 != spirvSize)
+ return false;
+ const uint32_t* words = (const uint32_t*)spirvData;
+ const uint32_t* wordsEnd = words + wordCount;
+ if (!smolv_CheckSpirVHeader(words, wordCount))
+ return false;
+ words += 5;
+
+ stats->inputCount++;
+ stats->totalSize += wordCount;
+
+ while (words < wordsEnd)
+ {
+ _SMOLV_READ_OP(instrLen, words, op);
+
+ if (op < kKnownOpsCount)
+ {
+ stats->opCounts[op]++;
+ stats->opSizes[op] += instrLen;
+ }
+ words += instrLen;
+ stats->totalOps++;
+ }
+
+ return true;
+}
+
+
+bool smolv::StatsCalculateSmol(smolv::Stats* stats, const void* smolvData, size_t smolvSize)
+{
+ if (!stats)
+ return false;
+
+ // debugging helper to dump all encoded bytes to stdout, keep at "if 0"
+# if 0
+# define _SMOLV_DEBUG_PRINT_ENCODED_BYTES() { \
+ printf("Op %-22s ", op < kKnownOpsCount ? kSpirvOpNames[op] : "???"); \
+ for (const uint8_t* b = instrBegin; b < bytes; ++b) \
+ printf("%02x ", *b); \
+ printf("\n"); \
+ }
+# else
+# define _SMOLV_DEBUG_PRINT_ENCODED_BYTES() {}
+# endif
+
+ const uint8_t* bytes = (const uint8_t*)smolvData;
+ const uint8_t* bytesEnd = bytes + smolvSize;
+ if (!smolv_CheckSmolHeader(bytes, smolvSize))
+ return false;
+
+ uint32_t val;
+ int smolVersion;
+ bytes += 4;
+ smolv_Read4(bytes, bytesEnd, val); smolVersion = val >> 24;
+ const int knownOpsCount = smolv_GetKnownOpsCount(smolVersion);
+ bytes += 16;
+
+ stats->totalSizeSmol += smolvSize;
+
+ while (bytes < bytesEnd)
+ {
+ const uint8_t* instrBegin = bytes;
+ const uint8_t* varBegin;
+
+ // read length + opcode
+ uint32_t instrLen;
+ SpvOp op;
+ varBegin = bytes;
+ if (!smolv_ReadLengthOp(bytes, bytesEnd, instrLen, op))
+ return false;
+ const bool wasSwizzle = (op == SpvOpVectorShuffleCompact);
+ if (wasSwizzle)
+ op = SpvOpVectorShuffle;
+ stats->varintCountsOp[bytes-varBegin]++;
+
+ size_t ioffs = 1;
+ if (smolv_OpHasType(op, knownOpsCount))
+ {
+ varBegin = bytes;
+ if (!smolv_ReadVarint(bytes, bytesEnd, val)) return false;
+ stats->varintCountsType[bytes-varBegin]++;
+ ioffs++;
+ }
+ if (smolv_OpHasResult(op, knownOpsCount))
+ {
+ varBegin = bytes;
+ if (!smolv_ReadVarint(bytes, bytesEnd, val)) return false;
+ stats->varintCountsRes[bytes-varBegin]++;
+ ioffs++;
+ }
+
+ if (op == SpvOpDecorate || op == SpvOpMemberDecorate)
+ {
+ if (!smolv_ReadVarint(bytes, bytesEnd, val)) return false;
+ ioffs++;
+ }
+ // MemberDecorate special decoding
+ if (op == SpvOpMemberDecorate)
+ {
+ if (bytes >= bytesEnd)
+ return false; // broken input
+ int count = *bytes++;
+ for (int m = 0; m < count; ++m)
+ {
+ uint32_t memberIndex;
+ if (!smolv_ReadVarint(bytes, bytesEnd, memberIndex)) return false;
+ uint32_t memberDec;
+ if (!smolv_ReadVarint(bytes, bytesEnd, memberDec)) return false;
+ const int knownExtraOps = smolv_DecorationExtraOps(memberDec);
+ uint32_t memberLen;
+ if (knownExtraOps == -1)
+ {
+ if (!smolv_ReadVarint(bytes, bytesEnd, memberLen)) return false;
+ memberLen += 4;
+ }
+ else
+ memberLen = 4 + knownExtraOps;
+ for (uint32_t i = 4; i < memberLen; ++i)
+ {
+ if (!smolv_ReadVarint(bytes, bytesEnd, val)) return false;
+ }
+ }
+ stats->smolOpSizes[op] += bytes - instrBegin;
+ _SMOLV_DEBUG_PRINT_ENCODED_BYTES();
+ continue;
+ }
+
+ int relativeCount = smolv_OpDeltaFromResult(op, knownOpsCount);
+ for (int i = 0; i < relativeCount && ioffs < instrLen; ++i, ++ioffs)
+ {
+ varBegin = bytes;
+ if (!smolv_ReadVarint(bytes, bytesEnd, val)) return false;
+ stats->varintCountsRes[bytes-varBegin]++;
+ }
+
+ if (wasSwizzle && instrLen <= 9)
+ {
+ bytes++;
+ }
+ else if (smolv_OpVarRest(op, knownOpsCount))
+ {
+ for (; ioffs < instrLen; ++ioffs)
+ {
+ varBegin = bytes;
+ if (!smolv_ReadVarint(bytes, bytesEnd, val)) return false;
+ stats->varintCountsOther[bytes-varBegin]++;
+ }
+ }
+ else
+ {
+ for (; ioffs < instrLen; ++ioffs)
+ {
+ if (!smolv_Read4(bytes, bytesEnd, val)) return false;
+ }
+ }
+
+ if (op < kKnownOpsCount)
+ {
+ stats->smolOpSizes[op] += bytes - instrBegin;
+ }
+ _SMOLV_DEBUG_PRINT_ENCODED_BYTES();
+ }
+
+ return true;
+}
+
+static bool CompareOpCounters (std::pair<SpvOp,size_t> a, std::pair<SpvOp,size_t> b)
+{
+ return a.second > b.second;
+}
+
+void smolv::StatsPrint(const Stats* stats)
+{
+ if (!stats)
+ return;
+
+ typedef std::pair<SpvOp,size_t> OpCounter;
+ OpCounter counts[kKnownOpsCount];
+ OpCounter sizes[kKnownOpsCount];
+ OpCounter sizesSmol[kKnownOpsCount];
+ for (int i = 0; i < kKnownOpsCount; ++i)
+ {
+ counts[i].first = (SpvOp)i;
+ counts[i].second = stats->opCounts[i];
+ sizes[i].first = (SpvOp)i;
+ sizes[i].second = stats->opSizes[i];
+ sizesSmol[i].first = (SpvOp)i;
+ sizesSmol[i].second = stats->smolOpSizes[i];
+ }
+ std::sort(counts, counts + kKnownOpsCount, CompareOpCounters);
+ std::sort(sizes, sizes + kKnownOpsCount, CompareOpCounters);
+ std::sort(sizesSmol, sizesSmol + kKnownOpsCount, CompareOpCounters);
+
+ printf("Stats for %i SPIR-V inputs, total size %i words (%.1fKB):\n", (int)stats->inputCount, (int)stats->totalSize, stats->totalSize * 4.0f / 1024.0f);
+ printf("Most occuring ops:\n");
+ for (int i = 0; i < 30; ++i)
+ {
+ SpvOp op = counts[i].first;
+ printf(" #%2i: %4i %-20s %4i (%4.1f%%)\n", i, op, kSpirvOpNames[op], (int)counts[i].second, (float)counts[i].second / (float)stats->totalOps * 100.0f);
+ }
+ printf("Largest total size of ops:\n");
+ for (int i = 0; i < 30; ++i)
+ {
+ SpvOp op = sizes[i].first;
+ printf(" #%2i: %-22s %6i (%4.1f%%) avg len %.1f\n",
+ i,
+ kSpirvOpNames[op],
+ (int)sizes[i].second*4,
+ (float)sizes[i].second / (float)stats->totalSize * 100.0f,
+ (float)sizes[i].second*4 / (float)stats->opCounts[op]
+ );
+ }
+ printf("SMOL varint encoding counts per byte length:\n");
+ printf(" B: %6s %6s %6s %6s\n", "Op", "Type", "Result", "Other");
+ for (int i = 1; i < 6; ++i)
+ {
+ printf(" %i: %6i %6i %6i %6i\n", i, (int)stats->varintCountsOp[i], (int)stats->varintCountsType[i], (int)stats->varintCountsRes[i], (int)stats->varintCountsOther[i]);
+ }
+ printf("Largest total size of ops in SMOL:\n");
+ for (int i = 0; i < 30; ++i)
+ {
+ SpvOp op = sizesSmol[i].first;
+ printf(" #%2i: %-22s %6i (%4.1f%%) avg len %.1f\n",
+ i,
+ kSpirvOpNames[op],
+ (int)sizesSmol[i].second,
+ (float)sizesSmol[i].second / (float)stats->totalSizeSmol * 100.0f,
+ (float)sizesSmol[i].second / (float)stats->opCounts[op]
+ );
+ }
+}
+
+
+// ------------------------------------------------------------------------------
+// This software is available under 2 licenses -- choose whichever you prefer.
+// ------------------------------------------------------------------------------
+// ALTERNATIVE A - MIT License
+// Copyright (c) 2016-2020 Aras Pranckevicius
+// 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.
+// ------------------------------------------------------------------------------
+// ALTERNATIVE B - Public Domain (www.unlicense.org)
+// This is free and unencumbered software released into the public domain.
+// Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
+// software, either in source code form or as a compiled binary, for any purpose,
+// commercial or non-commercial, and by any means.
+// In jurisdictions that recognize copyright laws, the author or authors of this
+// software dedicate any and all copyright interest in the software to the public
+// domain. We make this dedication for the benefit of the public at large and to
+// the detriment of our heirs and successors. We intend this dedication to be an
+// overt act of relinquishment in perpetuity of all present and future rights to
+// this software under copyright law.
+// 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 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.
+// ------------------------------------------------------------------------------
diff --git a/thirdparty/misc/smolv.h b/thirdparty/misc/smolv.h
new file mode 100644
index 0000000000..798ee4126f
--- /dev/null
+++ b/thirdparty/misc/smolv.h
@@ -0,0 +1,169 @@
+// smol-v - public domain - https://github.com/aras-p/smol-v
+// authored 2016-2020 by Aras Pranckevicius
+// no warranty implied; use at your own risk
+// See end of file for license information.
+//
+//
+// ### OVERVIEW:
+//
+// SMOL-V encodes Vulkan/Khronos SPIR-V format programs into a form that is smaller, and is more
+// compressible. Normally no changes to the programs are done; they decode
+// into exactly same program as what was encoded. Optionally, debug information
+// can be removed too.
+//
+// SPIR-V is a very verbose format, several times larger than same programs expressed in other
+// shader formats (e.g. DX11 bytecode, GLSL, DX9 bytecode etc.). The SSA-form with ever increasing
+// IDs is not very appreciated by regular data compressors either. SMOL-V does several things
+// to improve this:
+// - Many words, especially ones that most often have small values, are encoded using
+// "varint" scheme (1-5 bytes per word, with just one byte for values in 0..127 range).
+// See https://developers.google.com/protocol-buffers/docs/encoding
+// - Some IDs used in the program are delta-encoded, relative to previously seen IDs (e.g. Result
+// IDs). Often instructions reference things that were computed just before, so this results in
+// small deltas. These values are also encoded using "varint" scheme.
+// - Reordering instruction opcodes so that the most common ones are the smallest values, for smaller
+// varint encoding.
+// - Encoding several instructions in a more compact form, e.g. the "typical <=4 component swizzle"
+// shape of a VectorShuffle instruction, or sequences of MemberDecorate instructions.
+//
+// A somewhat similar utility is spirv-remap from glslang, see
+// https://github.com/KhronosGroup/glslang/blob/master/README-spirv-remap.txt
+//
+//
+// ### USAGE:
+//
+// Add source/smolv.h and source/smolv.cpp to your C++ project build.
+// Currently it might require C++11 or somesuch; I only tested with Visual Studio 2017/2019, Mac Xcode 11 and Gcc 5.4.
+//
+// smolv::Encode and smolv::Decode is the basic functionality.
+//
+// Other functions are for development/statistics purposes, to figure out frequencies and
+// distributions of the instructions.
+//
+// There's a test + compression benchmarking suite in testing/testmain.cpp; using that needs adding
+// other files under testing/external to the build too (3rd party code: glslang remapper, Zstd, LZ4).
+//
+//
+// ### LIMITATIONS / TODO:
+//
+// - SPIR-V where the words got stored in big-endian layout is not supported yet.
+// - The whole thing might not work on Big-Endian CPUs. It might, but I'm not 100% sure.
+// - Not much prevention is done against malformed/corrupted inputs, TODO.
+// - Out of memory cases are not handled. The code will either throw exception
+// or crash, depending on your compilation flags.
+
+#pragma once
+
+#include <stdint.h>
+#include <vector>
+#include <cstddef>
+
+namespace smolv
+{
+ typedef std::vector<uint8_t> ByteArray;
+
+ enum EncodeFlags
+ {
+ kEncodeFlagNone = 0,
+ kEncodeFlagStripDebugInfo = (1<<0), // Strip all optional SPIR-V instructions (debug names etc.)
+ };
+ enum DecodeFlags
+ {
+ kDecodeFlagNone = 0,
+ kDecodeFlagUse20160831AsZeroVersion = (1 << 0), // For "version zero" of SMOL-V encoding, use 2016 08 31 code path (this is what happens to be used by Unity 2017-2020)
+ };
+
+ // Preserve *some* OpName debug names.
+ // Return true to preserve, false to strip.
+ // This is really only used to implement a workaround for problems with some Vulkan drivers.
+ typedef bool(*StripOpNameFilterFunc)(const char* name);
+
+ // -------------------------------------------------------------------
+ // Encoding / Decoding
+
+ // Encode SPIR-V into SMOL-V.
+ //
+ // Resulting data is appended to outSmolv array (the array is not cleared).
+ //
+ // flags is bitset of EncodeFlags values.
+ //
+ // Returns false on malformed SPIR-V input; if that happens the output array might get
+ // partial/broken SMOL-V program.
+ bool Encode(const void* spirvData, size_t spirvSize, ByteArray& outSmolv, uint32_t flags = kEncodeFlagNone, StripOpNameFilterFunc stripFilter = 0);
+
+
+ // Decode SMOL-V into SPIR-V.
+ //
+ // Resulting data is written into the passed buffer. Get required buffer space with
+ // GetDecodeBufferSize; this is the size of decoded SPIR-V program.
+ //
+ // flags is bitset of DecodeFlags values.
+
+ // Decoding does no memory allocations.
+ //
+ // Returns false on malformed input; if that happens the output buffer might be only partially
+ // written to.
+ bool Decode(const void* smolvData, size_t smolvSize, void* spirvOutputBuffer, size_t spirvOutputBufferSize, uint32_t flags = kDecodeFlagNone);
+
+
+ // Given a SMOL-V program, get size of the decoded SPIR-V program.
+ // This is the buffer size that Decode expects.
+ //
+ // Returns zero on malformed input (just checks the header, not the full input).
+ size_t GetDecodedBufferSize(const void* smolvData, size_t smolvSize);
+
+
+ // -------------------------------------------------------------------
+ // Computing instruction statistics on SPIR-V/SMOL-V programs
+
+ struct Stats;
+
+ Stats* StatsCreate();
+ void StatsDelete(Stats* s);
+
+ bool StatsCalculate(Stats* stats, const void* spirvData, size_t spirvSize);
+ bool StatsCalculateSmol(Stats* stats, const void* smolvData, size_t smolvSize);
+ void StatsPrint(const Stats* stats);
+
+} // namespace smolv
+
+
+// ------------------------------------------------------------------------------
+// This software is available under 2 licenses -- choose whichever you prefer.
+// ------------------------------------------------------------------------------
+// ALTERNATIVE A - MIT License
+// Copyright (c) 2016-2020 Aras Pranckevicius
+// 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.
+// ------------------------------------------------------------------------------
+// ALTERNATIVE B - Public Domain (www.unlicense.org)
+// This is free and unencumbered software released into the public domain.
+// Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
+// software, either in source code form or as a compiled binary, for any purpose,
+// commercial or non-commercial, and by any means.
+// In jurisdictions that recognize copyright laws, the author or authors of this
+// software dedicate any and all copyright interest in the software to the public
+// domain. We make this dedication for the benefit of the public at large and to
+// the detriment of our heirs and successors. We intend this dedication to be an
+// overt act of relinquishment in perpetuity of all present and future rights to
+// this software under copyright law.
+// 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 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.
+// ------------------------------------------------------------------------------