Repository: godotengine/godot (language: cpp)

Issue:
Sometimes when exporting in "--headless" mode, shaders with "global uniform" doesn't work
### Tested versions

Reproduced: v4.3.stable.mono

### System information

Godot v4.3.stable.mono - Windows 10.0.19045 - Vulkan (Forward+) - dedicated NVIDIA GeForce RTX 2060 (NVIDIA; 32.0.15.6094) - AMD Ryzen 5 1600 Six-Core Processor (12 Threads)

### Issue description

Similar to https://github.com/godotengine/godot/issues/102362, which seems only visual, but in some cases might change into completely breaking a shader.
The issue happens when you have shader with global uniforms (correctly added to the Project Settings) and export it.

Behavior of exporting the project normally or via cmd and that of exporting it **via cmd with --headless** mode is different. Doing it the first method works correctly, doing it the second method breaks the shaders with global uniforms. 

But importantly, the working of the exported shader will be based on **how the project was exported for the first time** 

So:
1. Export normally -> shader works in the build
2. Export with --headless -> shader still work in the build

BUT:
1. Export with --headless -> shader does NOT work in the build
2. **Export normally -> shader STILL does NOT work in the build**

I traced this behavior to the ".godot/exported" folder - if you remove it, you "restart" the process, as above.

I noticed it first when setting up my CI pipeline (with Github Actions). As there is no .godot folder on the machine, and it uses --headless, it's gonna break the shaders every time. Only fix I found was including .godot/exported in the source control.


### Steps to reproduce

1. In empty project, create shader that uses "global uniform". Then create material using that shader and apply it on some mesh (not sure if required)
2. Add the required global uniforms in Project Settings
3. Remove the  ".godot/exported" folder (if exists)
4. Export the project using command line using "--headless" mode. (In case of MRP I use godot --headless --export-release "Windows Desktop" "build/Shader Test.exe")
5. Notice the shader does not use the uniform correctly in the build

### Minimal reproduction project (MRP)

[shader-test.zip](https://github.com/user-attachments/files/18642930/shader-test.zip)


Discussion / hints:

Fix patch:
diff --git a/servers/rendering/dummy/storage/material_storage.cpp b/servers/rendering/dummy/storage/material_storage.cpp
index e8b553ca7636..b22971a750cd 100644
--- a/servers/rendering/dummy/storage/material_storage.cpp
+++ b/servers/rendering/dummy/storage/material_storage.cpp
@@ -30,6 +30,8 @@
 
 #include "material_storage.h"
 
+#include "core/config/project_settings.h"
+
 using namespace RendererDummy;
 
 MaterialStorage *MaterialStorage::singleton = nullptr;
@@ -42,6 +44,103 @@ MaterialStorage::MaterialStorage() {
 
 MaterialStorage::~MaterialStorage() {
 	singleton = nullptr;
+	global_shader_variables.clear();
+}
+
+void MaterialStorage::global_shader_parameter_add(const StringName &p_name, RS::GlobalShaderParameterType p_type, const Variant &p_value) {
+	ERR_FAIL_COND(global_shader_variables.has(p_name));
+
+	global_shader_variables[p_name] = p_type;
+}
+
+void MaterialStorage::global_shader_parameter_remove(const StringName &p_name) {
+	if (!global_shader_variables.has(p_name)) {
+		return;
+	}
+
+	global_shader_variables.erase(p_name);
+}
+
+Vector<StringName> MaterialStorage::global_shader_parameter_get_list() const {
+	Vector<StringName> names;
+	for (const KeyValue<StringName, RS::GlobalShaderParameterType> &E : global_shader_variables) {
+		names.push_back(E.key);
+	}
+	names.sort_custom<StringName::AlphCompare>();
+	return names;
+}
+
+RS::GlobalShaderParameterType MaterialStorage::global_shader_parameter_get_type(const StringName &p_name) const {
+	if (!global_shader_variables.has(p_name)) {
+		print_line("don't have name, sorry");
+		return RS::GLOBAL_VAR_TYPE_MAX;
+	}
+
+	return global_shader_variables[p_name];
+}
+
+void MaterialStorage::global_shader_parameters_load_settings(bool p_load_textures) {
+	List<PropertyInfo> settings;
+	ProjectSettings::get_singleton()->get_property_list(&settings);
+
+	for (const PropertyInfo &E : settings) {
+		if (E.name.begins_with("shader_globals/")) {
+			StringName name = E.name.get_slice("/", 1);
+			Dictionary d = GLOBAL_GET(E.name);
+
+			ERR_CONTINUE(!d.has("type"));
+			ERR_CONTINUE(!d.has("value"));
+
+			String type = d["type"];
+
+			static const char *global_var_type_names[RS::GLOBAL_VAR_TYPE_MAX] = {
+				"bool",
+				"bvec2",
+				"bvec3",
+				"bvec4",
+				"int",
+				"ivec2",
+				"ivec3",
+				"ivec4",
+				"rect2i",
+				"uint",
+				"uvec2",
+				"uvec3",
+				"uvec4",
+				"float",
+				"vec2",
+				"vec3",
+				"vec4",
+				"color",
+				"rect2",
+				"mat2",
+				"mat3",
+				"mat4",
+				"transform_2d",
+				"transform",
+				"sampler2D",
+				"sampler2DArray",
+				"sampler3D",
+				"samplerCube",
+				"samplerExternalOES"
+			};
+
+			RS::GlobalShaderParameterType gvtype = RS::GLOBAL_VAR_TYPE_MAX;
+
+			for (int i = 0; i < RS::GLOBAL_VAR_TYPE_MAX; i++) {
+				if (global_var_type_names[i] == type) {
+					gvtype = RS::GlobalShaderParameterType(i);
+					break;
+				}
+			}
+
+			ERR_CONTINUE(gvtype == RS::GLOBAL_VAR_TYPE_MAX); //type invalid
+
+			if (!global_shader_variables.has(name)) {
+				global_shader_parameter_add(name, gvtype, Variant());
+			}
+		}
+	}
 }
 
 RID MaterialStorage::shader_allocate() {
@@ -131,3 +230,56 @@ void MaterialStorage::get_shader_parameter_list(RID p_shader, List<PropertyInfo>
 		p_param_list->push_back(pi);
 	}
 }
+
+RID MaterialStorage::material_allocate() {
+	return material_owner.allocate_rid();
+}
+
+void MaterialStorage::material_initialize(RID p_rid) {
+	material_owner.initialize_rid(p_rid, DummyMaterial());
+}
+
+void MaterialStorage::material_free(RID p_rid) {
+	DummyMaterial *material = material_owner.get_or_null(p_rid);
+	ERR_FAIL_NULL(material);
+
+	material_owner.free(p_rid);
+}
+
+void MaterialStorage::material_set_shader(RID p_material, RID p_shader) {
+	DummyMaterial *material = material_owner.get_or_null(p_material);
+	ERR_FAIL_NULL(material);
+
+	material->shader = p_shader;
+}
+
+void MaterialStorage::material_set_next_pass(RID p_material, RID p_next_material) {
+	DummyMaterial *material = material_owner.get_or_null(p_material);
+	ERR_FAIL_NULL(material);
+
+	material->next_pass = p_next_material;
+}
+
+void MaterialStorage::material_get_instance_shader_parameters(RID p_material, List<InstanceShaderParam> *r_parameters) {
+	DummyMaterial *material = material_owner.get_or_null(p_material);
+	ERR_FAIL_NULL(material);
+	DummyShader *shader = shader_owner.get_or_null(material->shader);
+
+	if (shader) {
+		for (const KeyValue<StringName, ShaderLanguage::ShaderNode::Uniform> &E : shader->uniforms) {
+			if (E.value.scope != ShaderLanguage::ShaderNode::Uniform::SCOPE_INSTANCE) {
+				continue;
+			}
+
+			RendererMaterialStorage::InstanceShaderParam p;
+			p.info = ShaderLanguage::uniform_to_property_info(E.value);
+			p.info.name = E.key; //supply name
+			p.index = E.value.instance_index;
+			p.default_value = ShaderLanguage::constant_value_to_variant(E.value.default_value, E.value.type, E.value.array_size, E.value.hint);
+			r_parameters->push_back(p);
+		}
+	}
+	if (material->next_pass.is_valid()) {
+		material_get_instance_shader_parameters(material->next_pass, r_parameters);
+	}
+}
diff --git a/servers/rendering/dummy/storage/material_storage.h b/servers/rendering/dummy/storage/material_storage.h
index 9422c395e2e6..ef46cf3b9b4f 100644
--- a/servers/rendering/dummy/storage/material_storage.h
+++ b/servers/rendering/dummy/storage/material_storage.h
@@ -42,6 +42,8 @@ class MaterialStorage : public RendererMaterialStorage {
 private:
 	static MaterialStorage *singleton;
 
+	HashMap<StringName, RS::GlobalShaderParameterType> global_shader_variables;
+
 	struct DummyShader {
 		HashMap<StringName, ShaderLanguage::ShaderNode::Uniform> uniforms;
 	};
@@ -50,6 +52,13 @@ class MaterialStorage : public RendererMaterialStorage {
 
 	ShaderCompiler dummy_compiler;
 
+	struct DummyMaterial {
+		RID shader;
+		RID next_pass;
+	};
+
+	mutable RID_Owner<DummyMaterial> material_owner;
+
 public:
 	static MaterialStorage *get_singleton() { return singleton; }
 
@@ -58,16 +67,16 @@ class MaterialStorage : public RendererMaterialStorage {
 
 	/* GLOBAL SHADER UNIFORM API */
 
-	virtual void global_shader_parameter_add(const StringName &p_name, RS::GlobalShaderParameterType p_type, const Variant &p_value) override {}
-	virtual void global_shader_parameter_remove(const StringName &p_name) override {}
-	virtual Vector<StringName> global_shader_parameter_get_list() const override { return Vector<StringName>(); }
+	virtual void global_shader_parameter_add(const StringName &p_name, RS::GlobalShaderParameterType p_type, const Variant &p_value) override;
+	virtual void global_shader_parameter_remove(const StringName &p_name) override;
+	virtual Vector<StringName> global_shader_parameter_get_list() const override;
 
 	virtual void global_shader_parameter_set(const StringName &p_name, const Variant &p_value) override {}
 	virtual void global_shader_parameter_set_override(const StringName &p_name, const Variant &p_value) override {}
 	virtual Variant global_shader_parameter_get(const StringName &p_name) const override { return Variant(); }
-	virtual RS::GlobalShaderParameterType global_shader_parameter_get_type(const StringName &p_name) const override { return RS::GLOBAL_VAR_TYPE_MAX; }
+	virtual RS::GlobalShaderParameterType global_shader_parameter_get_type(const StringName &p_name) const override;
 
-	virtual void global_shader_parameters_load_settings(bool p_load_textures = true) override {}
+	virtual void global_shader_parameters_load_settings(bool p_load_textures = true) override;
 	virtual void global_shader_parameters_clear() override {}
 
 	virtual int32_t global_shader_parameters_instance_allocate(RID p_instance) override { return 0; }
@@ -95,23 +104,26 @@ class MaterialStorage : public RendererMaterialStorage {
 	virtual RS::ShaderNativeSourceCode shader_get_native_source_code(RID p_shader) const override { return RS::ShaderNativeSourceCode(); }
 
 	/* MATERIAL API */
-	virtual RID material_allocate() override { return RID(); }
-	virtual void material_initialize(RID p_rid) override {}
-	virtual void material_free(RID p_rid) override {}
+
+	bool owns_material(RID p_rid) { return material_owner.owns(p_rid); }
+
+	virtual RID material_allocate() override;
+	virtual void material_initialize(RID p_rid) override;
+	virtual void material_free(RID p_rid) override;
 
 	virtual void material_set_render_priority(RID p_material, int priority) override {}
-	virtual void material_set_shader(RID p_shader_material, RID p_shader) override {}
+	virtual void material_set_shader(RID p_shader_material, RID p_shader) override;
 
 	virtual void material_set_param(RID p_material, const StringName &p_param, const Variant &p_value) override {}
 	virtual Variant material_get_param(RID p_material, const StringName &p_param) const override { return Variant(); }
 
-	virtual void material_set_next_pass(RID p_material, RID p_next_material) override {}
+	virtual void material_set_next_pass(RID p_material, RID p_next_material) override;
 
 	virtual bool material_is_animated(RID p_material) override { return false; }
 	virtual bool material_casts_shadows(RID p_material) override { return false; }
 	virtual RS::CullMode material_get_cull_mode(RID p_material) const override { return RS::CULL_MODE_DISABLED; }
 
-	virtual void material_get_instance_shader_parameters(RID p_material, List<InstanceShaderParam> *r_parameters) override {}
+	virtual void material_get_instance_shader_parameters(RID p_material, List<InstanceShaderParam> *r_parameters) override;
 	virtual void material_update_dependency(RID p_material, DependencyTracker *p_instance) override {}
 };
 
diff --git a/servers/rendering/dummy/storage/utilities.h b/servers/rendering/dummy/storage/utilities.h
index 4e20dee640e5..02e163fd8e8e 100644
--- a/servers/rendering/dummy/storage/utilities.h
+++ b/servers/rendering/dummy/storage/utilities.h
@@ -77,6 +77,9 @@ class Utilities : public RendererUtilities {
 		} else if (RendererDummy::MaterialStorage::get_singleton()->owns_shader(p_rid)) {
 			RendererDummy::MaterialStorage::get_singleton()->shader_free(p_rid);
 			return true;
+		} else if (RendererDummy::MaterialStorage::get_singleton()->owns_material(p_rid)) {
+			RendererDummy::MaterialStorage::get_singleton()->material_free(p_rid);
+			return true;
 		}
 		return false;
 	}


Test patch: