X Tutup
Skip to content

Fix VisualShader conversion failing with subresources#109375

Merged
Repiteo merged 1 commit intogodotengine:masterfrom
ColinSORourke:VisualShaderEmbedHandling
Sep 19, 2025
Merged

Fix VisualShader conversion failing with subresources#109375
Repiteo merged 1 commit intogodotengine:masterfrom
ColinSORourke:VisualShaderEmbedHandling

Conversation

@ColinSORourke
Copy link
Contributor

@ColinSORourke ColinSORourke commented Aug 6, 2025

(Attempts to) Resolve: #101375

Visual Shaders can have Subresources such as Textures embedded directly in the Visual Shader, and some users find this desirable to better preview the Visual Shader's behavior. However, this breaks the conversion from Visual Shader to Text GDShader because a Text GDshader cannot have SubResources (the appropriate place for these resources would be as a Shader Parameter on a ShaderMaterial).

This PR aligns with @QbieShay's suggestion that

I think that converting to shader a visual shader with embedded textures should just fail and print a warning/error.

And resolves the issue by adding a has_node_embeds function to the VisualShader class. Has_node_embeds iterates over the child nodes, and searches through the object properties for embedded resources. The conversion plugin calls this function to catch and throw appropriate errors/warnings.

Note: ConversionPlugin fail states can cause a crash in FileSystem_Dock.cpp - I've mirrored a catch for that from #109369 to here.

Screenshot 2025-08-06 at 1 57 49 PM

This PR could unblock #102918, which could decrease file sizes and improve load times. Would require a little more work to have that PRs code handle a failed conversion.

More ideal solutions could be developed in the future such as:

  • Disallowing subresource embeds in VisualShaders, and forcing resource references to go through the appropriate ShaderParameters on a Material Resource
  • Automatically making embeds 'unique' or creating a Material with the appropriate parameters upon conversion

But I believe this code is still a necessary step to resolve the core error (Conversion deleting subresources) and to track VisualShader subresources for compatibility with future changes.

@ColinSORourke ColinSORourke requested a review from a team as a code owner August 6, 2025 20:56
@ColinSORourke ColinSORourke requested a review from a team August 6, 2025 20:56
@ColinSORourke ColinSORourke requested a review from a team as a code owner August 6, 2025 20:56
@AThousandShips AThousandShips changed the title VisualShader Conversion fails with Embeds Fix VisualShader Conversion failing with subresources Aug 6, 2025
@AThousandShips AThousandShips added bug topic:editor topic:shaders cherrypick:4.4 Considered for cherry-picking into a future 4.4.x release cherrypick:4.5 Considered for cherry-picking into a future 4.5.x release labels Aug 7, 2025
@AThousandShips AThousandShips added this to the 4.6 milestone Aug 7, 2025
@KoBeWi
Copy link
Member

KoBeWi commented Aug 19, 2025

The problem with this solution is that VisualShader nodes have to count the embeds manually. If someone adds a new node with a resource, but does not mark embed, it will miss the error.

@ColinSORourke ColinSORourke force-pushed the VisualShaderEmbedHandling branch from 61b7700 to d3c4859 Compare August 19, 2025 12:18
@ColinSORourke ColinSORourke requested a review from a team as a code owner August 19, 2025 12:18
@ColinSORourke ColinSORourke force-pushed the VisualShaderEmbedHandling branch 2 times, most recently from db8c2ec to e0cc9f0 Compare August 19, 2025 12:23
@QbieShay
Copy link
Contributor

Came back to this. I wonder how we feel about adding a hint_default("res://blabla.png") ?

@ColinSORourke
Copy link
Contributor Author

ColinSORourke commented Aug 20, 2025

@KoBeWi

If someone adds a new node with a resource, but does not mark embed,

Assuming you are talking about VisualShaderNodeCustom - VisualShaderNodeCustom cannot have Resource Embeds, nothing in the current API provides that functionality unless I am misunderstanding.

@QbieShay

Came back to this. I wonder how we feel about adding a hint_default("res://blabla.png") ?

We talked about this in the Rendering Meeting today (Aug.20) (You were there :) ) and determined hint_default is not a workable feature.

Conclusion we reached in the Rendering Meeting for the issue is to throw a warning popup when the conversion plugin is called via Editor, and fall back to throwing an Error (like the current solution) when the conversion is initiated via code. I will adjust my fix to throw the popup when I have some spare time

@KoBeWi
Copy link
Member

KoBeWi commented Aug 20, 2025

Assuming you are talking about VisualShaderNodeCustom

No, adding a new visual shader node type in the engine. This PR modifies many classes, and in the future there may be more classes that will need similar checks.

@ColinSORourke
Copy link
Contributor Author

The problem with this solution is that VisualShader nodes have to count the embeds manually. If someone adds a new node with a resource, but does not mark embed, it will miss the error.
No, adding a new visual shader node type in the engine. This PR modifies many classes, and in the future there may be more classes that will need similar checks.

Tracking embeds is necessary to catch and communicate this bad conversion behavior. I think the responsibility of tracking embeds is best assigned to individual node classes. Currently only 6 VisualShaderNode classes can have embedded resources, and I would say safe handling of resources on future node classes is important, and low cost.

I have a hard time envisioning an alternative architecture that defers the responsibility of tracking embeds somewhere else? Managing this information from the Visual Shader Editor Plugin seems like it would require more work than having each node track itself, and I don't know if the Visual Shader resource Class has enough info to do any of this tracking.

@ColinSORourke ColinSORourke force-pushed the VisualShaderEmbedHandling branch from e0cc9f0 to c299253 Compare September 3, 2025 16:43
@ColinSORourke
Copy link
Contributor Author

@KoBeWi Talked about it in the rendering meeting today, and have updated the PR with a different architecture for the fix.

// Returns 0 if no embeds, 1 if external embeds, 2 if builtin embeds
int VisualShader::has_node_embeds() const {
	bool external_embeds = false;
	for (int i = 0; i < TYPE_MAX; i++) {
		for (const KeyValue<int, Node> &E : graph[i].nodes) {
			List<PropertyInfo> props;
			E.value.node->get_property_list(&props);
			// For classes that inherit from VisualShaderNode, the class properties start at the 12th, and the last value is always 'script'
			for (int i = 12; i < props.size() - 1; i++) {
				// VisualShaderNodeCustom cannot have embeds
				if (props.get(i).name == "VisualShaderNodeCustom") {
					break;
				}
				// Ref<Resource> properties get classed as type Variant::Object
				if (props.get(i).type == Variant::OBJECT) {
					Ref<Resource> res = E.value.node->get(props.get(i).name);
					if (res.is_valid()) {
						if (res->is_built_in()) {
							return 2;
						} else {
							external_embeds = true;
						}
					}
				}
			}
		}
	}

	return external_embeds;
}

This single function in the VisualShader class should be able to catch which child nodes have embedded resource properties without requiring any overhead on the VisualShaderNode classes

@ColinSORourke ColinSORourke force-pushed the VisualShaderEmbedHandling branch from c299253 to 71849c8 Compare September 3, 2025 16:52
@KoBeWi
Copy link
Member

KoBeWi commented Sep 7, 2025

I tested the MRP from #101375 with the newest version of this PR and it doesn't seem to do anything. Conversion is still broken and there is no error/warning. Maybe I'm doing something wrong, idk.

@ColinSORourke ColinSORourke force-pushed the VisualShaderEmbedHandling branch from 71849c8 to 1bd95e5 Compare September 7, 2025 21:11
@ColinSORourke
Copy link
Contributor Author

@KoBeWi My bad! Silly mistake, I've pushed a fix.

I changed an inner loop variable name 'i'->'j' to satisfy the code check that inner loop should not use same name as outer loop, but forget to replace in a few relevant locations. Fixed, tested, works.

@ColinSORourke ColinSORourke force-pushed the VisualShaderEmbedHandling branch from 1bd95e5 to 3983d2d Compare September 16, 2025 19:34
int embed = vshader->has_node_embeds();
ERR_FAIL_COND_V_MSG(embed == 2, Ref<Resource>(), "Cannot convert VisualShader to GDShader because VisualShader has embedded subresources.");
if (embed == 1) {
WARN_PRINT("Visual Shader conversion cannot convert embedded resources. Resource references from Nodes will have to be rebound as ShaderParameters on a Material");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"embedded" is not correct here. They are not embedded, they are external dependencies of the shader.

btw are the shader parameters automatically created?

Copy link
Contributor Author

@ColinSORourke ColinSORourke Sep 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the language on the error.

As far as I can tell, yes the Shader Parameters are automatically created. A Texture2D Node (which could have an embedded texture) is converted to a Uniform in the GD Shader Code, which will be recognized as a Shader Parameter.

The VisualShader in the MRP has 2 Texture2D Nodes, they convert to code

uniform sampler2D tex_frg_44;
uniform sampler2D tex_frg_43;

And when the converted GDShader is attached to a ShaderMaterial, the uniforms show up as a parameter again

g->nodes[p_id].position = p_position;
}

// Returns 0 if no embeds, 1 if external embeds, 2 if builtin embeds
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sounds like it should be an enum, but since it's an editor one-time helper, hard-coding is fine too.

@KoBeWi
Copy link
Member

KoBeWi commented Sep 16, 2025

Looks fine now, but I'd change the warning to a toaster message, as it's easy to miss.

Also converting the shader from the inspector results in clearing the property if there are sub-resources.

godot.windows.editor.dev.x86_64_F7AZEe8BRj.mp4

This could be avoided by returning the same shader instead of null.

@ColinSORourke ColinSORourke force-pushed the VisualShaderEmbedHandling branch from 3983d2d to 8657fe3 Compare September 16, 2025 23:30
@ColinSORourke
Copy link
Contributor Author

ColinSORourke commented Sep 16, 2025

@KoBeWi Updated warning language, and changed to throw a Toaster Message (when it can - I think there are cases where a conversion plugin can be invoked outside of editor)

Also converting the shader from the inspector results in clearing the property if there are sub-resources.
This could be avoided by returning the same shader instead of null.

Made this update too, although I think it'd be preferable if the Inspector Dock handled a null return and recognized the value as unchanged. Looking for the file that manages that ;)

@KoBeWi
Copy link
Member

KoBeWi commented Sep 17, 2025

Looks like easy fix:

edited_resource = conversions[to_type]->convert(edited_resource);

@ColinSORourke ColinSORourke force-pushed the VisualShaderEmbedHandling branch from 8657fe3 to cabad7a Compare September 17, 2025 20:37
@ColinSORourke
Copy link
Contributor Author

Added a catch in Editor_Resource_Picker conversion to handle being given null by a failed conversion. Returned the Conversion plugin to always return null in a failed conversion.

Also added emit_signal(SNAME("file_removed") to the FileSystemDock conversion action - without this, if a VisualShader was converted to Shader, the ShaderEditor would keep the VisualShader version open as a hanging [unsaved]

@ColinSORourke ColinSORourke force-pushed the VisualShaderEmbedHandling branch from cabad7a to 914b482 Compare September 18, 2025 21:03
@ColinSORourke
Copy link
Contributor Author

ColinSORourke commented Sep 18, 2025

Pushed a build addressing @KoBeWi and @AThousandShips comments.

Potentially resolves godotengine#101375

VisualShader now has a has_node_embeds function that runs through it's child nodes to find embedded resources via object properties. Conversion plugin uses this function to catch the error.
@ColinSORourke ColinSORourke force-pushed the VisualShaderEmbedHandling branch from 914b482 to c4559c0 Compare September 18, 2025 21:35
@Repiteo Repiteo merged commit 2753d33 into godotengine:master Sep 19, 2025
20 checks passed
@Repiteo
Copy link
Contributor

Repiteo commented Sep 19, 2025

Thanks!

@ColinSORourke ColinSORourke deleted the VisualShaderEmbedHandling branch November 29, 2025 03:30
@akien-mga akien-mga removed cherrypick:4.4 Considered for cherry-picking into a future 4.4.x release cherrypick:4.5 Considered for cherry-picking into a future 4.5.x release labels Jan 16, 2026
@akien-mga
Copy link
Member

Cherry-picked for 4.5.2.

@akien-mga akien-mga changed the title Fix VisualShader Conversion failing with subresources Fix VisualShader conversion failing with subresources Jan 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Converting visual shader to shader loses subresources

6 participants

X Tutup