Pārlūkot izejas kodu

Merge pull request #49050 from reduz/implement-spirv-cache

Implement shader caching
Rémi Verschelde 4 gadi atpakaļ
vecāks
revīzija
596eb783fe
39 mainītis faili ar 3113 papildinājumiem un 172 dzēšanām
  1. 6 0
      COPYRIGHT.txt
  2. 1 0
      core/SCsub
  3. 8 0
      core/config/engine.cpp
  4. 5 0
      core/config/engine.h
  5. 6 0
      doc/classes/EditorInterface.xml
  6. 49 0
      doc/classes/EditorPaths.xml
  7. 0 9
      doc/classes/EditorSettings.xml
  8. 10 0
      doc/classes/ProjectSettings.xml
  9. 5 5
      editor/editor_export.cpp
  10. 12 3
      editor/editor_node.cpp
  11. 1 0
      editor/editor_node.h
  12. 156 0
      editor/editor_paths.cpp
  13. 72 0
      editor/editor_paths.h
  14. 5 0
      editor/editor_plugin.cpp
  15. 2 0
      editor/editor_plugin.h
  16. 1 1
      editor/editor_resource_preview.cpp
  17. 18 87
      editor/editor_settings.cpp
  18. 1 7
      editor/editor_settings.h
  19. 1 1
      editor/export_template_manager.cpp
  20. 4 4
      editor/plugins/asset_library_editor_plugin.cpp
  21. 1 1
      editor/plugins/editor_preview_plugins.cpp
  22. 6 0
      main/main.cpp
  23. 7 0
      modules/glslang/register_types.cpp
  24. 2 2
      modules/mono/godotsharp_dirs.cpp
  25. 2 2
      platform/android/export/export.cpp
  26. 3 3
      platform/javascript/export/export.cpp
  27. 4 4
      platform/osx/export/export.cpp
  28. 3 3
      platform/uwp/export/export.cpp
  29. 42 0
      servers/rendering/renderer_rd/renderer_compositor_rd.cpp
  30. 1 1
      servers/rendering/renderer_rd/renderer_compositor_rd.h
  31. 65 36
      servers/rendering/renderer_rd/shader_compiler_rd.cpp
  32. 289 1
      servers/rendering/renderer_rd/shader_rd.cpp
  33. 21 2
      servers/rendering/renderer_rd/shader_rd.h
  34. 12 0
      servers/rendering/rendering_device.cpp
  35. 4 0
      servers/rendering/rendering_device.h
  36. 6 0
      servers/rendering_server.cpp
  37. 5 0
      thirdparty/README.md
  38. 2108 0
      thirdparty/misc/smolv.cpp
  39. 169 0
      thirdparty/misc/smolv.h

+ 6 - 0
COPYRIGHT.txt

@@ -360,6 +360,12 @@ Comment: SMAZ
 Copyright: 2006-2009, Salvatore Sanfilippo
 Copyright: 2006-2009, Salvatore Sanfilippo
 License: BSD-3-clause
 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
 Files: ./thirdparty/misc/stb_rect_pack.h
  ./thirdparty/misc/stb_vorbis.c
  ./thirdparty/misc/stb_vorbis.c
 Comment: stb libraries
 Comment: stb libraries

+ 1 - 0
core/SCsub

@@ -59,6 +59,7 @@ thirdparty_misc_sources = [
     "pcg.cpp",
     "pcg.cpp",
     "polypartition.cpp",
     "polypartition.cpp",
     "clipper.cpp",
     "clipper.cpp",
+    "smolv.cpp",
 ]
 ]
 thirdparty_misc_sources = [thirdparty_misc_dir + file for file in thirdparty_misc_sources]
 thirdparty_misc_sources = [thirdparty_misc_dir + file for file in thirdparty_misc_sources]
 env_thirdparty.add_source_files(thirdparty_obj, thirdparty_misc_sources)
 env_thirdparty.add_source_files(thirdparty_obj, thirdparty_misc_sources)

+ 8 - 0
core/config/engine.cpp

@@ -31,6 +31,7 @@
 #include "engine.h"
 #include "engine.h"
 
 
 #include "core/authors.gen.h"
 #include "core/authors.gen.h"
+#include "core/config/project_settings.h"
 #include "core/donors.gen.h"
 #include "core/donors.gen.h"
 #include "core/license.gen.h"
 #include "core/license.gen.h"
 #include "core/version.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::singleton = nullptr;
 
 
 Engine *Engine::get_singleton() {
 Engine *Engine::get_singleton() {

+ 5 - 0
core/config/engine.h

@@ -72,6 +72,8 @@ private:
 
 
 	static Engine *singleton;
 	static Engine *singleton;
 
 
+	String shader_cache_path;
+
 public:
 public:
 	static Engine *get_singleton();
 	static Engine *get_singleton();
 
 
@@ -121,6 +123,9 @@ public:
 	Dictionary get_license_info() const;
 	Dictionary get_license_info() const;
 	String get_license_text() 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_abort_on_gpu_errors_enabled() const;
 	bool is_validation_layers_enabled() const;
 	bool is_validation_layers_enabled() const;
 
 

+ 6 - 0
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.
 				[b]Note:[/b] This returns the main editor control containing the whole editor, not the 2D or 3D viewports specifically.
 			</description>
 			</description>
 		</method>
 		</method>
+		<method name="get_editor_paths">
+			<return type="EditorPaths">
+			</return>
+			<description>
+			</description>
+		</method>
 		<method name="get_editor_scale" qualifiers="const">
 		<method name="get_editor_scale" qualifiers="const">
 			<return type="float">
 			<return type="float">
 			</return>
 			</return>

+ 49 - 0
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>

+ 0 - 9
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.
 				Returns the value of the setting specified by [code]name[/code]. This is equivalent to using [method Object.get] on the EditorSettings instance.
 			</description>
 			</description>
 		</method>
 		</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">
 		<method name="has_setting" qualifiers="const">
 			<return type="bool">
 			<return type="bool">
 			</return>
 			</return>

+ 10 - 0
doc/classes/ProjectSettings.xml

@@ -1502,6 +1502,16 @@
 		<member name="rendering/reflections/sky_reflections/texture_array_reflections.mobile" type="bool" setter="" getter="" default="false">
 		<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.
 			Lower-end override for [member rendering/reflections/sky_reflections/texture_array_reflections] on mobile devices, due to performance concerns or driver support.
 		</member>
 		</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">
 		<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.
 			If [code]true[/code], uses faster but lower-quality Blinn model to generate blurred reflections instead of the GGX model.
 		</member>
 		</member>

+ 5 - 5
editor/editor_export.cpp

@@ -1065,7 +1065,7 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> &
 				}
 				}
 			} else {
 			} else {
 				// Use default text server data.
 				// 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);
 				TS->save_support_data(icu_data_file);
 				Vector<uint8_t> array = FileAccess::get_file_as_array(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);
 				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 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);
 	ProjectSettings::get_singleton()->save_custom(engine_cfb, custom_map, custom_list);
 	Vector<uint8_t> data = FileAccess::get_file_as_array(engine_cfb);
 	Vector<uint8_t> data = FileAccess::get_file_as_array(engine_cfb);
 	DirAccess::remove_file_or_error(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.
 	// Create the temporary export directory if it doesn't exist.
 	DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
 	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);
 	FileAccess *ftmp = FileAccess::open(tmppath, FileAccess::WRITE);
 	ERR_FAIL_COND_V_MSG(!ftmp, ERR_CANT_CREATE, "Cannot create file '" + tmppath + "'.");
 	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) {
 	if (!convert) {
 		return;
 		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);
 	Error err = ResourceFormatLoaderText::convert_file_to_binary(p_path, tmp_path);
 	if (err != OK) {
 	if (err != OK) {
 		DirAccess::remove_file_or_error(tmp_path);
 		DirAccess::remove_file_or_error(tmp_path);

+ 12 - 3
editor/editor_node.cpp

@@ -80,6 +80,7 @@
 #include "editor/editor_inspector.h"
 #include "editor/editor_inspector.h"
 #include "editor/editor_layouts_dialog.h"
 #include "editor/editor_layouts_dialog.h"
 #include "editor/editor_log.h"
 #include "editor/editor_log.h"
+#include "editor/editor_paths.h"
 #include "editor/editor_plugin.h"
 #include "editor/editor_plugin.h"
 #include "editor/editor_properties.h"
 #include "editor/editor_properties.h"
 #include "editor/editor_resource_picker.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);
 			img->convert(Image::FORMAT_RGB8);
 
 
 			//save thumbnail directly, as thumbnailer may not update due to actual scene not changing md5
 			//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();
 			String cache_base = ProjectSettings::get_singleton()->globalize_path(p_file).md5_text();
 			cache_base = temp_path.plus_file("resthumb-" + cache_base);
 			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();
 			settings_config_dialog->popup_edit_settings();
 		} break;
 		} break;
 		case SETTINGS_EDITOR_DATA_FOLDER: {
 		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;
 		} break;
 		case SETTINGS_EDITOR_CONFIG_FOLDER: {
 		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;
 		} break;
 		case SETTINGS_MANAGE_EXPORT_TEMPLATES: {
 		case SETTINGS_MANAGE_EXPORT_TEMPLATES: {
 			export_template_manager->popup_manager();
 			export_template_manager->popup_manager();
@@ -3727,10 +3728,15 @@ bool EditorNode::is_scene_in_use(const String &p_path) {
 	return false;
 	return false;
 }
 }
 
 
+void EditorNode::register_editor_paths(bool p_for_project_manager) {
+	EditorPaths::create(p_for_project_manager);
+}
+
 void EditorNode::register_editor_types() {
 void EditorNode::register_editor_types() {
 	ResourceLoader::set_timestamp_on_load(true);
 	ResourceLoader::set_timestamp_on_load(true);
 	ResourceSaver::set_timestamp_on_save(true);
 	ResourceSaver::set_timestamp_on_save(true);
 
 
+	ClassDB::register_class<EditorPaths>();
 	ClassDB::register_class<EditorPlugin>();
 	ClassDB::register_class<EditorPlugin>();
 	ClassDB::register_class<EditorTranslationParserPlugin>();
 	ClassDB::register_class<EditorTranslationParserPlugin>();
 	ClassDB::register_class<EditorImportPlugin>();
 	ClassDB::register_class<EditorImportPlugin>();
@@ -3774,6 +3780,9 @@ void EditorNode::register_editor_types() {
 
 
 void EditorNode::unregister_editor_types() {
 void EditorNode::unregister_editor_types() {
 	_init_callbacks.clear();
 	_init_callbacks.clear();
+	if (EditorPaths::get_singleton()) {
+		EditorPaths::free();
+	}
 }
 }
 
 
 void EditorNode::stop_child_process(OS::ProcessID p_pid) {
 void EditorNode::stop_child_process(OS::ProcessID p_pid) {

+ 1 - 0
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);
 	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 register_editor_types();
 	static void unregister_editor_types();
 	static void unregister_editor_types();
 
 

+ 156 - 0
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");
+		}
+	}
+}

+ 72 - 0
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

+ 5 - 0
editor/editor_plugin.cpp

@@ -32,6 +32,7 @@
 
 
 #include "editor/editor_export.h"
 #include "editor/editor_export.h"
 #include "editor/editor_node.h"
 #include "editor/editor_node.h"
+#include "editor/editor_paths.h"
 #include "editor/editor_settings.h"
 #include "editor/editor_settings.h"
 #include "editor/filesystem_dock.h"
 #include "editor/filesystem_dock.h"
 #include "editor/project_settings_editor.h"
 #include "editor/project_settings_editor.h"
@@ -257,6 +258,9 @@ EditorSelection *EditorInterface::get_selection() {
 Ref<EditorSettings> EditorInterface::get_editor_settings() {
 Ref<EditorSettings> EditorInterface::get_editor_settings() {
 	return EditorSettings::get_singleton();
 	return EditorSettings::get_singleton();
 }
 }
+EditorPaths *EditorInterface::get_editor_paths() {
+	return EditorPaths::get_singleton();
+}
 
 
 EditorResourcePreview *EditorInterface::get_resource_previewer() {
 EditorResourcePreview *EditorInterface::get_resource_previewer() {
 	return EditorResourcePreview::get_singleton();
 	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_selected_path"), &EditorInterface::get_selected_path);
 	ClassDB::bind_method(D_METHOD("get_current_path"), &EditorInterface::get_current_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_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("set_plugin_enabled", "plugin", "enabled"), &EditorInterface::set_plugin_enabled);
 	ClassDB::bind_method(D_METHOD("is_plugin_enabled", "plugin"), &EditorInterface::is_plugin_enabled);
 	ClassDB::bind_method(D_METHOD("is_plugin_enabled", "plugin"), &EditorInterface::is_plugin_enabled);

+ 2 - 0
editor/editor_plugin.h

@@ -54,6 +54,7 @@ class EditorNode3DGizmoPlugin;
 class EditorResourcePreview;
 class EditorResourcePreview;
 class EditorFileSystem;
 class EditorFileSystem;
 class EditorToolAddons;
 class EditorToolAddons;
+class EditorPaths;
 class FileSystemDock;
 class FileSystemDock;
 class ScriptEditor;
 class ScriptEditor;
 
 
@@ -95,6 +96,7 @@ public:
 	EditorSelection *get_selection();
 	EditorSelection *get_selection();
 	//EditorImportExport *get_import_export();
 	//EditorImportExport *get_import_export();
 	Ref<EditorSettings> get_editor_settings();
 	Ref<EditorSettings> get_editor_settings();
+	EditorPaths *get_editor_paths();
 	EditorResourcePreview *get_resource_previewer();
 	EditorResourcePreview *get_resource_previewer();
 	EditorFileSystem *get_resource_file_system();
 	EditorFileSystem *get_resource_file_system();
 
 

+ 1 - 1
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);
 					_preview_ready(item.path + ":" + itos(item.resource->hash_edited_version()), texture, small_texture, item.id, item.function, item.userdata);
 
 
 				} else {
 				} 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();
 					String cache_base = ProjectSettings::get_singleton()->globalize_path(item.path).md5_text();
 					cache_base = temp_path.plus_file("resthumb-" + cache_base);
 					cache_base = temp_path.plus_file("resthumb-" + cache_base);
 
 

+ 18 - 87
editor/editor_settings.cpp

@@ -902,67 +902,26 @@ void EditorSettings::create() {
 		return; //pointless
 		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);
 	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) {
 		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
 	ClassDB::register_class<EditorSettings>(); //otherwise it can't be unserialized
 
 
 	String config_file_path;
 	String config_file_path;
 
 
-	if (data_path != "" && config_path != "" && cache_path != "") {
+	if (EditorPaths::get_singleton()->are_paths_valid()) {
 		// Validate/create data dir and subdirectories
 		// Validate/create data dir and subdirectories
 
 
+		String data_dir = EditorPaths::get_singleton()->get_data_dir();
+
 		dir = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
 		dir = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
 		if (dir->change_dir(data_dir) != OK) {
 		if (dir->change_dir(data_dir) != OK) {
 			dir->make_dir_recursive(data_dir);
 			dir->make_dir_recursive(data_dir);
@@ -979,22 +938,11 @@ void EditorSettings::create() {
 			dir->change_dir("..");
 			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
 		// 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!");
 				ERR_PRINT("Cannot create config directory!");
 				memdelete(dir);
 				memdelete(dir);
 				goto fail;
 				goto fail;
@@ -1035,7 +983,7 @@ void EditorSettings::create() {
 		// Validate editor config file
 		// Validate editor config file
 
 
 		String config_file_name = "editor_settings-" + itos(VERSION_MAJOR) + ".tres";
 		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)) {
 		if (!dir->file_exists(config_file_name)) {
 			memdelete(dir);
 			memdelete(dir);
 			goto fail;
 			goto fail;
@@ -1052,9 +1000,6 @@ void EditorSettings::create() {
 
 
 		singleton->save_changed_setting = true;
 		singleton->save_changed_setting = true;
 		singleton->config_file_path = config_file_path;
 		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!");
 		print_verbose("EditorSettings: Load OK!");
 
 
@@ -1069,6 +1014,8 @@ void EditorSettings::create() {
 fail:
 fail:
 
 
 	// patch init projects
 	// patch init projects
+	String exe_path = OS::get_singleton()->get_executable_path().get_base_dir();
+
 	if (extra_config->has_section("init_projects")) {
 	if (extra_config->has_section("init_projects")) {
 		Vector<String> list = extra_config->get_value("init_projects", "list");
 		Vector<String> list = extra_config->get_value("init_projects", "list");
 		for (int i = 0; i < list.size(); i++) {
 		for (int i = 0; i < list.size(); i++) {
@@ -1080,9 +1027,6 @@ fail:
 	singleton = Ref<EditorSettings>(memnew(EditorSettings));
 	singleton = Ref<EditorSettings>(memnew(EditorSettings));
 	singleton->save_changed_setting = true;
 	singleton->save_changed_setting = true;
 	singleton->config_file_path = config_file_path;
 	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->_load_defaults(extra_config);
 	singleton->setup_language();
 	singleton->setup_language();
 	singleton->setup_network();
 	singleton->setup_network();
@@ -1312,30 +1256,22 @@ void EditorSettings::add_property_hint(const PropertyInfo &p_hint) {
 
 
 // Data directories
 // Data directories
 
 
-String EditorSettings::get_data_dir() const {
-	return data_dir;
-}
-
 String EditorSettings::get_templates_dir() const {
 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
 // Config directories
 
 
-String EditorSettings::get_settings_dir() const {
-	return settings_dir;
-}
-
 String EditorSettings::get_project_settings_dir() const {
 String EditorSettings::get_project_settings_dir() const {
 	return EditorSettings::PROJECT_EDITOR_SETTINGS_PATH;
 	return EditorSettings::PROJECT_EDITOR_SETTINGS_PATH;
 }
 }
 
 
 String EditorSettings::get_text_editor_themes_dir() const {
 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 {
 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 {
 String EditorSettings::get_project_script_templates_dir() const {
@@ -1344,12 +1280,8 @@ String EditorSettings::get_project_script_templates_dir() const {
 
 
 // Cache directory
 // Cache directory
 
 
-String EditorSettings::get_cache_dir() const {
-	return cache_dir;
-}
-
 String EditorSettings::get_feature_profiles_dir() const {
 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
 // Metadata
@@ -1576,7 +1508,7 @@ Vector<String> EditorSettings::get_script_templates(const String &p_extension, c
 }
 }
 
 
 String EditorSettings::get_editor_layouts_config() const {
 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
 // Shortcuts
@@ -1778,7 +1710,6 @@ void EditorSettings::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("property_get_revert", "name"), &EditorSettings::property_get_revert);
 	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("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("get_project_settings_dir"), &EditorSettings::get_project_settings_dir);
 
 
 	ClassDB::bind_method(D_METHOD("set_project_metadata", "section", "key", "data"), &EditorSettings::set_project_metadata);
 	ClassDB::bind_method(D_METHOD("set_project_metadata", "section", "key", "data"), &EditorSettings::set_project_metadata);

+ 1 - 7
editor/editor_settings.h

@@ -36,6 +36,7 @@
 #include "core/object/class_db.h"
 #include "core/object/class_db.h"
 #include "core/os/thread_safe.h"
 #include "core/os/thread_safe.h"
 #include "core/string/translation.h"
 #include "core/string/translation.h"
+#include "editor/editor_paths.h"
 #include "scene/gui/shortcut.h"
 #include "scene/gui/shortcut.h"
 
 
 class EditorPlugin;
 class EditorPlugin;
@@ -87,12 +88,7 @@ private:
 	mutable Map<String, Ref<Shortcut>> shortcuts;
 	mutable Map<String, Ref<Shortcut>> shortcuts;
 	Map<String, List<Ref<InputEvent>>> builtin_action_overrides;
 	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 config_file_path;
-	String project_config_dir;
 
 
 	Vector<String> favorites;
 	Vector<String> favorites;
 	Vector<String> recent_dirs;
 	Vector<String> recent_dirs;
@@ -153,12 +149,10 @@ public:
 
 
 	String get_data_dir() const;
 	String get_data_dir() const;
 	String get_templates_dir() const;
 	String get_templates_dir() const;
-	String get_settings_dir() const;
 	String get_project_settings_dir() const;
 	String get_project_settings_dir() const;
 	String get_text_editor_themes_dir() const;
 	String get_text_editor_themes_dir() const;
 	String get_script_templates_dir() const;
 	String get_script_templates_dir() const;
 	String get_project_script_templates_dir() const;
 	String get_project_script_templates_dir() const;
-	String get_cache_dir() const;
 	String get_feature_profiles_dir() const;
 	String get_feature_profiles_dir() const;
 
 
 	void set_project_metadata(const String &p_section, const String &p_key, Variant p_data);
 	void set_project_metadata(const String &p_section, const String &p_key, Variant p_data);

+ 1 - 1
editor/export_template_manager.cpp

@@ -444,7 +444,7 @@ void ExportTemplateManager::_begin_template_download(const String &p_url) {
 	}
 	}
 
 
 	download_data.clear();
 	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);
 	download_templates->set_use_threads(true);
 
 
 	Error err = download_templates->request(p_url);
 	Error err = download_templates->request(p_url);

+ 4 - 4
editor/plugins/asset_library_editor_plugin.cpp

@@ -464,7 +464,7 @@ void EditorAssetLibraryItemDownload::_make_request() {
 	retry->hide();
 	retry->hide();
 
 
 	download->cancel_request();
 	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);
 	Error err = download->request(host);
 	if (err != OK) {
 	if (err != OK) {
@@ -702,7 +702,7 @@ void EditorAssetLibrary::_image_update(bool use_cache, bool final, const PackedB
 		PackedByteArray image_data = p_data;
 		PackedByteArray image_data = p_data;
 
 
 		if (use_cache) {
 		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);
 			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) {
 		if (p_code != HTTPClient::RESPONSE_NOT_MODIFIED) {
 			for (int i = 0; i < headers.size(); i++) {
 			for (int i = 0; i < headers.size(); i++) {
 				if (headers[i].findn("ETag:") == 0) { // Save etag
 				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();
 					String new_etag = headers[i].substr(headers[i].find(":") + 1, headers[i].length()).strip_edges();
 					FileAccess *file;
 					FileAccess *file;
 
 
@@ -829,7 +829,7 @@ void EditorAssetLibrary::_update_image_queue() {
 	List<int> to_delete;
 	List<int> to_delete;
 	for (Map<int, ImageQueue>::Element *E = image_queue.front(); E; E = E->next()) {
 	for (Map<int, ImageQueue>::Element *E = image_queue.front(); E; E = E->next()) {
 		if (!E->get().active && current_images < max_images) {
 		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;
 			Vector<String> headers;
 
 
 			if (FileAccess::exists(cache_filename_base + ".etag") && FileAccess::exists(cache_filename_base + ".data")) {
 			if (FileAccess::exists(cache_filename_base + ".etag") && FileAccess::exists(cache_filename_base + ".data")) {

+ 1 - 1
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 {
 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();
 	String cache_base = ProjectSettings::get_singleton()->globalize_path(p_path).md5_text();
 	cache_base = temp_path.plus_file("resthumb-" + cache_base);
 	cache_base = temp_path.plus_file("resthumb-" + cache_base);
 
 

+ 6 - 0
main/main.cpp

@@ -1445,6 +1445,12 @@ Error Main::setup2(Thread::ID p_main_tid_override) {
 	}
 	}
 #endif
 #endif
 
 
+#ifdef TOOLS_ENABLED
+	if (editor || project_manager) {
+		EditorNode::register_editor_paths(project_manager);
+	}
+#endif
+
 	/* Determine text driver */
 	/* Determine text driver */
 
 
 	if (text_driver == "") {
 	if (text_driver == "") {

+ 7 - 0
modules/glslang/register_types.cpp

@@ -179,11 +179,18 @@ static Vector<uint8_t> _compile_shader_glsl(RenderingDevice::ShaderStage p_stage
 	return ret;
 	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() {
 void preregister_glslang_types() {
 	// initialize in case it's not initialized. This is done once per thread
 	// initialize in case it's not initialized. This is done once per thread
 	// and it's safe to call multiple times
 	// and it's safe to call multiple times
 	glslang::InitializeProcess();
 	glslang::InitializeProcess();
 	RenderingDevice::shader_set_compile_function(_compile_shader_glsl);
 	RenderingDevice::shader_set_compile_function(_compile_shader_glsl);
+	RenderingDevice::shader_set_get_cache_key_function(_get_cache_key_function_glsl);
 }
 }
 
 
 void register_glslang_types() {
 void register_glslang_types() {

+ 2 - 2
modules/mono/godotsharp_dirs.cpp

@@ -63,8 +63,8 @@ String _get_expected_build_config() {
 
 
 String _get_mono_user_dir() {
 String _get_mono_user_dir() {
 #ifdef TOOLS_ENABLED
 #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 {
 	} else {
 		String settings_path;
 		String settings_path;
 
 

+ 2 - 2
platform/android/export/export.cpp

@@ -1793,7 +1793,7 @@ public:
 			p_debug_flags |= DEBUG_FLAG_REMOTE_DEBUG_LOCALHOST;
 			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)                         \
 #define CLEANUP_AND_RETURN(m_err)                         \
 	{                                                     \
 	{                                                     \
@@ -2651,7 +2651,7 @@ public:
 		FileAccess *dst_f = nullptr;
 		FileAccess *dst_f = nullptr;
 		io2.opaque = &dst_f;
 		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)                            \
 #define CLEANUP_AND_RETURN(m_err)                            \
 	{                                                        \
 	{                                                        \

+ 3 - 3
platform/javascript/export/export.cpp

@@ -63,7 +63,7 @@ private:
 	}
 	}
 
 
 	void _set_internal_certs(Ref<Crypto> p_crypto) {
 	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 key_path = cache_path.plus_file("html5_server.key");
 		const String crt_path = cache_path.plus_file("html5_server.crt");
 		const String crt_path = cache_path.plus_file("html5_server.crt");
 		bool regen = !FileAccess::exists(key_path) || !FileAccess::exists(crt_path);
 		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_file = req[1].get_file();
 		const String req_ext = req[1].get_extension();
 		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);
 		const String filepath = cache_path.plus_file(req_file);
 
 
 		if (!mimes.has(req_ext) || !FileAccess::exists(filepath)) {
 		if (!mimes.has(req_ext) || !FileAccess::exists(filepath)) {
@@ -888,7 +888,7 @@ Error EditorExportPlatformJavaScript::run(const Ref<EditorExportPreset> &p_prese
 		return OK;
 		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);
 	DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
 	if (!da->dir_exists(dest)) {
 	if (!da->dir_exists(dest)) {
 		Error err = da->make_dir_recursive(dest);
 		Error err = da->make_dir_recursive(dest);

+ 4 - 4
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) {
 		if (icon_infos[i].is_png) {
 			// Encode PNG icon.
 			// Encode PNG icon.
 			it->create_from_image(copy);
 			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);
 			ResourceSaver::save(path, it);
 
 
 			FileAccess *f = FileAccess::open(path, FileAccess::READ);
 			FileAccess *f = FileAccess::open(path, FileAccess::READ);
@@ -610,7 +610,7 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
 
 
 	// Create our application bundle.
 	// Create our application bundle.
 	String tmp_app_dir_name = pkg_name + ".app";
 	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);
 	print_line("Exporting to " + tmp_app_path_name);
 
 
 	Error err = OK;
 	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");
 		String ent_path = p_preset->get("codesign/entitlements/custom_file");
 		if (sign_enabled && (ent_path == "")) {
 		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);
 			FileAccess *ent_f = FileAccess::open(ent_path, FileAccess::WRITE);
 			if (ent_f) {
 			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);
 				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);
 				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);
 				zipClose(zip, nullptr);
 			}
 			}

+ 3 - 3
platform/uwp/export/export.cpp

@@ -567,7 +567,7 @@ void AppxPackager::finish() {
 	// Create and add block map file
 	// Create and add block map file
 	EditorNode::progress_task_step("export", "Creating block map...", 4);
 	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);
 	make_block_map(tmp_blockmap_file_path);
 
 
 	FileAccess *blockmap_file = FileAccess::open(tmp_blockmap_file_path, FileAccess::READ);
 	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);
 	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);
 	make_content_types(tmp_content_types_file_path);
 
 
 	FileAccess *types_file = FileAccess::open(tmp_content_types_file_path, FileAccess::READ);
 	FileAccess *types_file = FileAccess::open(tmp_content_types_file_path, FileAccess::READ);
@@ -879,7 +879,7 @@ class EditorExportPlatformUWP : public EditorExportPlatform {
 			return data;
 			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);
 		Error err = texture->get_image()->save_png(tmp_path);
 
 

+ 42 - 0
servers/rendering/renderer_rd/renderer_compositor_rd.cpp

@@ -31,6 +31,7 @@
 #include "renderer_compositor_rd.h"
 #include "renderer_compositor_rd.h"
 
 
 #include "core/config/project_settings.h"
 #include "core/config/project_settings.h"
+#include "core/os/dir_access.h"
 
 
 void RendererCompositorRD::prepare_for_blitting_render_targets() {
 void RendererCompositorRD::prepare_for_blitting_render_targets() {
 	RD::get_singleton()->prepare_screen_for_drawing();
 	RD::get_singleton()->prepare_screen_for_drawing();
@@ -155,6 +156,43 @@ void RendererCompositorRD::finalize() {
 RendererCompositorRD *RendererCompositorRD::singleton = nullptr;
 RendererCompositorRD *RendererCompositorRD::singleton = nullptr;
 
 
 RendererCompositorRD::RendererCompositorRD() {
 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;
 	singleton = this;
 	time = 0;
 	time = 0;
 
 
@@ -171,3 +209,7 @@ RendererCompositorRD::RendererCompositorRD() {
 		scene = memnew(RendererSceneRenderImplementation::RenderForwardClustered(storage));
 		scene = memnew(RendererSceneRenderImplementation::RenderForwardClustered(storage));
 	}
 	}
 }
 }
+
+RendererCompositorRD::~RendererCompositorRD() {
+	ShaderRD::set_shader_cache_dir(String());
+}

+ 1 - 1
servers/rendering/renderer_rd/renderer_compositor_rd.h

@@ -118,6 +118,6 @@ public:
 
 
 	static RendererCompositorRD *singleton;
 	static RendererCompositorRD *singleton;
 	RendererCompositorRD();
 	RendererCompositorRD();
-	~RendererCompositorRD() {}
+	~RendererCompositorRD();
 };
 };
 #endif // RASTERIZER_RD_H
 #endif // RASTERIZER_RD_H

+ 65 - 36
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);
 	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()) {
 	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
 			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;
 		SL::FunctionNode *fnode = nullptr;
 
 
 		for (int i = 0; i < p_node->functions.size(); i++) {
 		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;
 				fnode = p_node->functions[i].function;
 				break;
 				break;
 			}
 			}
@@ -427,9 +434,9 @@ void ShaderCompilerRD::_dump_function_deps(const SL::ShaderNode *p_node, const S
 
 
 		header += ")\n";
 		header += ")\n";
 		r_to_add += header;
 		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);
 			uniform_defines.resize(max_uniforms);
 			bool uses_uniforms = false;
 			bool uses_uniforms = false;
 
 
+			Vector<StringName> uniform_names;
+
 			for (Map<StringName, SL::ShaderNode::Uniform>::Element *E = pnode->uniforms.front(); E; E = E->next()) {
 			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;
 				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.
 					//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
 					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) {
 				if (is_buffer_global) {
 					//this is an integer to index the global table
 					//this is an integer to index the global table
 					ucode += _typestr(ShaderLanguage::TYPE_UINT);
 					ucode += _typestr(ShaderLanguage::TYPE_UINT);
 				} else {
 				} 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";
 				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++) {
 					for (int j = 0; j < STAGE_MAX; j++) {
 						r_gen_code.stage_globals[j] += ucode;
 						r_gen_code.stage_globals[j] += ucode;
 					}
 					}
 
 
 					GeneratedCode::Texture texture;
 					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) {
 					if (texture.global) {
 						r_gen_code.uses_global_textures = true;
 						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 {
 				} else {
 					if (!uses_uniforms) {
 					if (!uses_uniforms) {
 						uses_uniforms = true;
 						uses_uniforms = true;
 					}
 					}
-					uniform_defines.write[E->get().order] = ucode;
+					uniform_defines.write[uniform.order] = ucode;
 					if (is_buffer_global) {
 					if (is_buffer_global) {
 						//globals are indices into the global table
 						//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 {
 					} 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++) {
 			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;
 			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()) {
 			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;
 					continue;
 				}
 				}
 
 
 				String vcode;
 				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 += "[";
-					vcode += itos(E->get().array_size);
+					vcode += itos(varying.array_size);
 					vcode += "]";
 					vcode += "]";
 				}
 				}
 				vcode += ";\n";
 				vcode += ";\n";

+ 289 - 1
servers/rendering/renderer_rd/shader_rd.cpp

@@ -30,8 +30,12 @@
 
 
 #include "shader_rd.h"
 #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 "renderer_compositor_rd.h"
 #include "servers/rendering/rendering_device.h"
 #include "servers/rendering/rendering_device.h"
+#include "thirdparty/misc/smolv.h"
 
 
 void ShaderRD::_add_stage(const char *p_code, StageType p_stage_type) {
 void ShaderRD::_add_stage(const char *p_code, StageType p_stage_type) {
 	Vector<String> lines = String(p_code).split("\n");
 	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) {
 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;
 	name = p_name;
+
 	if (p_compute_code) {
 	if (p_compute_code) {
 		_add_stage(p_compute_code, STAGE_TYPE_COMPUTE);
 		_add_stage(p_compute_code, STAGE_TYPE_COMPUTE);
 		is_compute = true;
 		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);
 			_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() {
 RID ShaderRD::version_create() {
@@ -131,6 +148,9 @@ void ShaderRD::_clear_version(Version *p_version) {
 		}
 		}
 
 
 		memdelete_arr(p_version->variants);
 		memdelete_arr(p_version->variants);
+		if (p_version->variant_stages) {
+			memdelete_arr(p_version->variant_stages);
+		}
 		p_version->variants = nullptr;
 		p_version->variants = nullptr;
 	}
 	}
 }
 }
@@ -183,7 +203,7 @@ void ShaderRD::_compile_variant(uint32_t p_variant, Version *p_version) {
 		return; //variant is disabled, return
 		return; //variant is disabled, return
 	}
 	}
 
 
-	Vector<RD::ShaderStageData> stages;
+	Vector<RD::ShaderStageData> &stages = p_version->variant_stages[p_variant];
 
 
 	String error;
 	String error;
 	String current_source;
 	String current_source;
@@ -313,6 +333,197 @@ RS::ShaderNativeSourceCode ShaderRD::version_get_native_source_code(RID p_versio
 	return source_code;
 	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) {
 void ShaderRD::_compile_version(Version *p_version) {
 	_clear_version(p_version);
 	_clear_version(p_version);
 
 
@@ -320,6 +531,15 @@ void ShaderRD::_compile_version(Version *p_version) {
 	p_version->dirty = false;
 	p_version->dirty = false;
 
 
 	p_version->variants = memnew_arr(RID, variant_defines.size());
 	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
 #if 1
 
 
 	RendererThreadPool::singleton->thread_work_pool.do_work(variant_defines.size(), this, &ShaderRD::_compile_variant, p_version);
 	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);
 		memdelete_arr(p_version->variants);
+		if (p_version->variant_stages) {
+			memdelete_arr(p_version->variant_stages);
+		}
 		p_version->variants = nullptr;
 		p_version->variants = nullptr;
+		p_version->variant_stages = nullptr;
 		return;
 		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;
 	p_version->valid = true;
 }
 }
 
 
@@ -443,6 +673,8 @@ bool ShaderRD::is_variant_enabled(int p_variant) const {
 	return variants_enabled[p_variant];
 	return variants_enabled[p_variant];
 }
 }
 
 
+bool ShaderRD::shader_cache_cleanup_on_start = false;
+
 ShaderRD::ShaderRD() {
 ShaderRD::ShaderRD() {
 	// Do not feel forced to use this, in most cases it makes little to no difference.
 	// Do not feel forced to use this, in most cases it makes little to no difference.
 	bool use_32_threads = false;
 	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());
 		variant_defines.push_back(p_variant_defines[i].utf8());
 		variants_enabled.push_back(true);
 		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() {
 ShaderRD::~ShaderRD() {
 	List<RID> remaining;
 	List<RID> remaining;
 	version_owner.get_owned_list(&remaining);
 	version_owner.get_owned_list(&remaining);

+ 21 - 2
servers/rendering/renderer_rd/shader_rd.h

@@ -59,7 +59,8 @@ class ShaderRD {
 		Map<StringName, CharString> code_sections;
 		Map<StringName, CharString> code_sections;
 		Vector<CharString> custom_defines;
 		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 valid;
 		bool dirty;
 		bool dirty;
@@ -96,10 +97,19 @@ class ShaderRD {
 
 
 	bool is_compute = false;
 	bool is_compute = false;
 
 
-	const char *name;
+	String name;
 
 
 	CharString base_compute_defines;
 	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 {
 	enum StageType {
 		STAGE_TYPE_VERTEX,
 		STAGE_TYPE_VERTEX,
 		STAGE_TYPE_FRAGMENT,
 		STAGE_TYPE_FRAGMENT,
@@ -113,6 +123,10 @@ class ShaderRD {
 
 
 	void _add_stage(const char *p_code, StageType p_stage_type);
 	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:
 protected:
 	ShaderRD();
 	ShaderRD();
 	void setup(const char *p_vertex_code, const char *p_fragment_code, const char *p_compute_code, const char *p_name);
 	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);
 	void set_variant_enabled(int p_variant, bool p_enabled);
 	bool is_variant_enabled(int p_variant) const;
 	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);
 	RS::ShaderNativeSourceCode version_get_native_source_code(RID p_version);
 
 
 	void initialize(const Vector<String> &p_variant_defines, const String &p_general_defines = "");
 	void initialize(const Vector<String> &p_variant_defines, const String &p_general_defines = "");

+ 12 - 0
servers/rendering/rendering_device.cpp

@@ -40,6 +40,7 @@ RenderingDevice *RenderingDevice::get_singleton() {
 
 
 RenderingDevice::ShaderCompileFunction RenderingDevice::compile_function = nullptr;
 RenderingDevice::ShaderCompileFunction RenderingDevice::compile_function = nullptr;
 RenderingDevice::ShaderCacheFunction RenderingDevice::cache_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) {
 void RenderingDevice::shader_set_compile_function(ShaderCompileFunction p_function) {
 	compile_function = p_function;
 	compile_function = p_function;
@@ -49,6 +50,10 @@ void RenderingDevice::shader_set_cache_function(ShaderCacheFunction p_function)
 	cache_function = 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) {
 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) {
 	if (p_allow_cache && cache_function) {
 		Vector<uint8_t> cache = cache_function(p_stage, p_source_code, p_language);
 		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);
 	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) {
 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_format.is_null(), RID());
 	ERR_FAIL_COND_V(p_view.is_null(), RID());
 	ERR_FAIL_COND_V(p_view.is_null(), RID());

+ 4 - 0
servers/rendering/rendering_device.h

@@ -103,12 +103,14 @@ public:
 		bool supports_multiview = false; // If true this device supports multiview options
 		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> (*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);
 	typedef Vector<uint8_t> (*ShaderCacheFunction)(ShaderStage p_stage, const String &p_source_code, ShaderLanguage p_language);
 
 
 private:
 private:
 	static ShaderCompileFunction compile_function;
 	static ShaderCompileFunction compile_function;
 	static ShaderCacheFunction cache_function;
 	static ShaderCacheFunction cache_function;
+	static ShaderGetCacheKeyFunction get_cache_key_function;
 
 
 	static RenderingDevice *singleton;
 	static RenderingDevice *singleton;
 
 
@@ -635,9 +637,11 @@ public:
 	const Capabilities *get_device_capabilities() const { return &device_capabilities; };
 	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 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_compile_function(ShaderCompileFunction p_function);
 	static void shader_set_cache_function(ShaderCacheFunction p_function);
 	static void shader_set_cache_function(ShaderCacheFunction p_function);
+	static void shader_set_get_cache_key_function(ShaderGetCacheKeyFunction p_function);
 
 
 	struct ShaderStageData {
 	struct ShaderStageData {
 		ShaderStage shader_stage;
 		ShaderStage shader_stage;

+ 6 - 0
servers/rendering_server.cpp

@@ -2306,6 +2306,12 @@ RenderingServer::RenderingServer() {
 					"rendering/vulkan/rendering/back_end",
 					"rendering/vulkan/rendering/back_end",
 					PROPERTY_HINT_ENUM, "ForwardClustered,ForwardMobile"));
 					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/roughness_layers", 8);
 	GLOBAL_DEF("rendering/reflections/sky_reflections/texture_array_reflections", true);
 	GLOBAL_DEF("rendering/reflections/sky_reflections/texture_array_reflections", true);
 	GLOBAL_DEF("rendering/reflections/sky_reflections/texture_array_reflections.mobile", false);
 	GLOBAL_DEF("rendering/reflections/sky_reflections/texture_array_reflections.mobile", false);

+ 5 - 0
thirdparty/README.md

@@ -469,6 +469,10 @@ Collection of single-file libraries used in Godot components.
   * Version: git (2f625846a775501fb69456567409a8b12f10ea25, 2012)
   * Version: git (2f625846a775501fb69456567409a8b12f10ea25, 2012)
   * License: BSD-3-Clause
   * License: BSD-3-Clause
   * Modifications: use `const char*` instead of `char*` for input string
   * 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`
 - `stb_rect_pack.h`
   * Upstream: https://github.com/nothings/stb
   * Upstream: https://github.com/nothings/stb
   * Version: 1.00 (2bb4a0accd4003c1db4c24533981e01b1adfd656, 2019)
   * Version: 1.00 (2bb4a0accd4003c1db4c24533981e01b1adfd656, 2019)
@@ -731,3 +735,4 @@ Files extracted from upstream source:
 
 
 - lib/{common/,compress/,decompress/,zstd.h}
 - lib/{common/,compress/,decompress/,zstd.h}
 - LICENSE
 - LICENSE
+

+ 2108 - 0
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.
+// ------------------------------------------------------------------------------

+ 169 - 0
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.
+// ------------------------------------------------------------------------------