Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Convert 3.x ESCN skeletons, animations, and shaders upon import #87106

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

nikitalita
Copy link
Contributor

@nikitalita nikitalita commented Jan 12, 2024

Depends on #88009, #88078

The Problem

ESCN was a discrete scene format that was created by the Godot team for ease-of-export from Blender to Godot. The plugin was originally written for Godot 3.x and used its scene format, and has not since been updated.

However, while Godot still has support for importing this format, ESCN importing has been broken since Godot 4.0 was released due significant changes in the scene format that conversion was not implemented for:

  • Skeletons had a Transform pose property which was relative to bone rest in 3.x , which was broken out to Vector3 pose_position; Quaternion pose_rotation; Vector3 pose_scale that are absolute in 4.x.
  • Animations used to have a transform track type with transforms relative to bone rest in 3.x, which was similarly broken out into position_3d, rotation_3d, and scale types that are absolute in 4.x
  • The shaders that were included in the ESCN export were of the 3.x format, which is not compatible with 4.x.

Solution

This adds the following features to the ESCN editor importer:

  1. Converts 3.x Skeletons to use the new pose properties and recomputes them to be absolute rather than relative to the bone rest.
  2. Converts 3.x Animations with transform tracks into position, rotation, and scale tracks that are absolute rather than relative to the bone rest.
  3. Converts old shader code to 4.x-compliant shader code

Design Considerations

The reason this is a draft right now is because of the following issues that I need advice on. This will likely be broken up into seperate PRs based on the feedback.

ResourceLoader modification to allow special handlers for a single load

I had issues with converting Animations and Shaders; basically, we can't handle converting them in either of the _set() functions for these classes like we can for Skeleton3D because:

  • Animation tracks are stored ini style:

    image

    This means that, upon load, each of those indexed properties gets set, one at a time. We need to split the transformation track up into position, rotation, and scale tracks, and we can't arbitrarily add tracks in the middle of a load, since the index of the tracks will change.

  • Shaders compile their code upon setting the code property; if the code fails to compile, code won't be set. Additionally, it will result in a very, very long error message that will obliterate the error logs and significantly confuse the user.

  • Both of those class names are the same from 3.x to 4.x, so I couldn't implement another class for either of those to handle the loading either.

So, to solve these issues, I modified the ResourceLoader to allow the addition of special handlers. Basically, we can add a handler for a specified Resource class that takes in a MissingResource and returns a Resource. If we try to load a resource, and the resource loader detects that it has a handler for this resource class, it will instead load the resource as a MissingResource and, after the resource and its properties are finished loading, it will hand it off to the handler to instantiate the resource and set its properties.

I had considered modifying the ResourceLoader classes to just add the property to missing_resource_properties if it fails to be set and then just getting those properties out of the metadata, but as mentioned above, this would result in a bunch of error messages (1000+ lines in total length) every time we tried to import an ESCN, which is not ideal.

The way I have it now, I'm not really sure if this is the correct design; this sort of special handling needs to be only for a single load rather than a global handler, so I decided to only add it to the instantiated ResourceLoader classes rather than the static ResourceFormatLoader classes. This breaks the pattern a bit, as it looks like it's intended that the user doesn't use the instantiated ResourceLoader class and instead uses the ResourceFormatLoader class. I'd appreciate some advice on how to implement this such that it can be used for a single load and doesn't break the pattern here.

ShaderLanguage token stream emitting

Converting Shaders from 3.x to 4.x is a bit more complicated than how it's currently handled in the 3.x to 4.x converter. In order to do a proper conversion of a 3.x to 4.x, we need to do the following:

  • Replace everything in RenamesMap3To4::shaders_renames
  • SCREEN_TEXTURE, DEPTH_TEXTURE, and NORMAL_ROUGHNESS_TEXTURE were removed, so their usage necessitates adding a uniform declaration at the top of the file
  • If shader_type is "particles", we need to rename the function void vertex() to void process()
  • CLEARCOAT_GLOSS was changed to CLEARCOAT_ROUGHNESS and its usage was inverted (i.e. ascending values now increase the roughness rather than decreasing it). So, we need to invert all usages of CLEARCOAT_GLOSS:
    • invert all lefthand assignments:
      • CLEARCOAT_GLOSS = 5.0 / foo;
        • becomes: CLEARCOAT_ROUGHNESS = (1.0 - (5.0 / foo));,
      • CLEARCOAT_GLOSS *= 1.1;
        • becomes CLEARCOAT_ROUGHNESS = (1.0 - ((1.0 - CLEARCOAT_ROUGHNESS) * 1.1));
    • invert all righthand usages
      • foo = CLEARCOAT_GLOSS;
        • becomes: foo = (1.0 - CLEARCOAT_ROUGHNESS);
  • async_visible and async_hidden were removed, so these render modes need to be removed
  • specular_blinn and specular_phong render modes were removed, and we can't handle these, so we just throw an error
  • MODULATE is not supported (yet) in 4.x, so we have to throw an error.

As you can see, this is a bit more complicated than a simple regex search and replace would allow for, in particular the CLEARCOAT_GLOSS replacement. We at least need a token stream in order to properly detect for these conditions and modify the code.

So, in order to do this, I modified ShaderLanguage to emit a token stream:

  • The function token_debug_stream() was added to emit a parsed token stream for the entire file.
  • Token was modified to add position and length members so that we can emit code based on the token stream; this is neccesary primarily because the token parser can modify both identifiers and constant values before creating the token.
  • The tokens TK_TAB, TK_CR ,TK_SPACE, TK_NEWLINE, TK_BLOCK_COMMENT, TK_LINE_COMMENT, and TK_PREPROC_DIRECTIVE were added and _get_token() was modified to emit them, but only if we are doing a debug parse. The reason for adding these was so that we can easily emit the new code from the token stream without obliterating the original formatting and comments

With these modifications, we're able to easily detect for the above conditions and insert and remove tokens from the token stream, and then emit code based on that token stream.

I'm a bit more confident about how I modified ShaderLanguage here but I'd still like to get feedback on how I modified ShaderLanguage here, and if there are any other 3.x to 4.x differences that I need to handle that I missed.

@AThousandShips
Copy link
Member

AThousandShips commented Jan 12, 2024

These are several unrelated improvements, please make a PR for each feature (you do already have PRs for these open, these changes should be in one PR only unless the code in this depends on it, in that case please keep the details of that in its own PR and mention that this depends on that PR)

@nikitalita
Copy link
Contributor Author

These are several unrelated improvements, please make a PR for each feature

That's the plan, they're not going to stay in this PR. This is just my working branch so I can get feedback on specific design questions.

@AThousandShips
Copy link
Member

Thats not really how you should do things, each issue or feature should be discussed separately, and you shouldn't open a PR for your working branch but work on each feature on one branch unless they're dependent on each other 🙂, this just takes up processing and duplicates things

@nikitalita
Copy link
Contributor Author

Ok, I have removed the extraneous commits and specified which PRs this depends on.

TokenStreamManip(const String &p_code) :
old_code(p_code) {
ShaderLanguage sl;
sl.token_debug_stream(old_code, code_tokens, true);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

token_debug_stream

@lyuma
Copy link
Contributor

lyuma commented Jan 14, 2024

I think this adds too much complexity to the import system which is not really the point of the importer.

If the goal is to add back compat with 3.x resources, we should do it one of two ways:

  1. expose them with a conversion utility (and perhaps the escn importer could invoke this conversion utility code automatically, but this way it isn't adding complexity to the import system itself). This code will have a maintenance cost to maintain.
  2. fix data in the resource loader or the property setters.

My personal opinion is that if this is to be done automatically, it should be done in the resources itself, which you attempted to explain why it wasn't possible, but I think it should be made to be possible. For example, we have discussed a compatibility system. We know which Godot version the resources originated from, so perhaps we can add the missing parts to the resource loader so that we can adapt the properties. These would be a good usecase for such a compatibility system.

Animation tracks are stored ini style:

This means that, upon load, each of those indexed properties gets set, one at a time. We need to split the transformation track up into position, rotation, and scale tracks, and we can't arbitrarily add tracks in the middle of a load, since the index of the tracks will change.

Yes, but you will know how much the index changes: you can store an offset that starts at 0 and increases by 2 for every transform track. In principle, the indices could be offset as the properties are set. It certainly is challenging and may be impossible without some changes to the core resource loader, but I do think we should be willing to look outside of the box rather than adding a lot of complexity to the import system.

Shaders compile their code upon setting the code property; if the code fails to compile, code won't be set. Additionally, it will result in a very, very long error message that will obliterate the error logs and significantly confuse the user.

What if when the code fails to compile, it could attempt to run it through the above converter and use that converted code if it succeeds?

Both of those class names are the same from 3.x to 4.x, so I couldn't implement another class for either of those to handle the loading either.

Yeah I don't think we want to add compatibility code especially at runtime. Load-time or editor-time conversion could be supported only if TOOLS_ENABLED, for example.

Do note also that if we do go this route, we need to make sure mesh compatibility issues are solved .

related issues:

@nikitalita
Copy link
Contributor Author

So what we talked about in the rocketchat was that the best course of action for modifying the resource loading to accommodate this would be to instead add void _start_load(int format, bool binary) and void _finish_load(int format, bool binary) virtual methods (that are NOPs by default) to Resource to accommodate this. the binary and text resource loaders would call _start_load() when the sub/main resource is instantiated, before any properties are loaded, and _finish_load() after all the properties have been read and set on the instanced resource.

For Animation, the main problem is that the transform tracks need to be split into seperate tracks, and all the properties for the tracks are set one at a time.

So for Animation, the steps would be:

  1. if _start_load() is called, set a flag in the metadata
  2. in _set() in Animation.cpp, if the _start_load() flag is set and if we read in a track with a transform type, store it and all of its subsequent properties in the resource metadata
  3. once _finish_load() is called, get those tracks out of the metadata, and then split them into position_3d, rotation_3d, and scale_3d tracks, and then clear the metadata of the flag and the transform track data.

This would still entail some steps in the scene importer; we would still have to recompute the animation frames to be absolute rather than relative to the skeleton rest, but we already do plenty of that in the scene importer for other scene types.

For Shader, the main issues are:

  1. the code is compiled as soon as the code property is set, which does not allow for fixing it after the fact,
  2. failed compilations will spit out 1000+ line error messages
  3. We don't want to auto-convert any shaders that aren't actually from Godot 3.x.

For Shader, the steps would be:

  1. If _start_load() is called, AND the format value is binary format 3 or text format 2 (i.e. the resource format versions used for all Godot 3.x releases), then we set a flag in the metadata
  2. in set_code() in Shader.cpp, if the flag is set:
    1. instead of compiling right away, first run the code through the ShaderLanguage parser (this is done to avoid printing the huge error messages that accompany failed shader compiles)
    2. If the ShaderLanguage parser fails, THEN we do some additional tests to see if it is in fact a 3.x shader (i.e. checking for old identifiers)
    3. If our tests indiciate that it is a 3.x shader, we run it through the code converter
    4. If the code conversion succeeds, then we compile and set the code property.

So, these steps would only be run on Shaders that are embeded in other resources (i.e. would not automatically be run for standalone gdshader files), and would only be run if the resource is in a deprecated format. This will prevent the "Unity shader auto-upgrade" shenanigans that @lyuma expressed their wishes in avoiding.

@nikitalita nikitalita force-pushed the convert-3.x-escn branch 3 times, most recently from 8323270 to 7dc2cbb Compare February 7, 2024 21:01
core/io/resource.h Outdated Show resolved Hide resolved
editor/plugins/text_shader_editor.cpp Show resolved Hide resolved
scene/3d/mesh_instance_3d.cpp Show resolved Hide resolved
scene/resources/animation.cpp Outdated Show resolved Hide resolved
scene/resources/animation.cpp Outdated Show resolved Hide resolved
servers/rendering/shader_converter.cpp Outdated Show resolved Hide resolved
servers/rendering/shader_converter.cpp Outdated Show resolved Hide resolved
servers/rendering/shader_converter.cpp Outdated Show resolved Hide resolved
servers/rendering/shader_converter.cpp Outdated Show resolved Hide resolved
servers/rendering/shader_converter.cpp Outdated Show resolved Hide resolved
@nikitalita nikitalita force-pushed the convert-3.x-escn branch 2 times, most recently from 1bf5044 to b0a9c87 Compare February 10, 2024 04:30
@nikitalita nikitalita marked this pull request as ready for review April 25, 2024 19:12
@nikitalita nikitalita requested review from a team as code owners April 25, 2024 19:12
@nikitalita nikitalita requested a review from a team as a code owner September 30, 2024 10:33
@nikitalita nikitalita force-pushed the convert-3.x-escn branch 2 times, most recently from 3ebdfc0 to d2b4da6 Compare September 30, 2024 11:22
@nikitalita
Copy link
Contributor Author

depends on #88078

@nikitalita nikitalita force-pushed the convert-3.x-escn branch 2 times, most recently from 018f1ed to 7cc7deb Compare September 30, 2024 15:58
@Zireael07
Copy link
Contributor

I have a project that contains ESCN, that needs this PR to make it in....

@nikitalita
Copy link
Contributor Author

grab one of the editor artifacts from the build jobs, import it, and then save it as a godot 4 scene :)

Copy link
Member

@fire fire left a comment

Choose a reason for hiding this comment

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

I'm under the weather, but I want some progress on this.

Some of the core io apis changed and I can't figure if they're necessary.

The changes are a bit invasive.

@clayjohn probably has opinions about the shader_converter.cpp

@nikitalita nikitalita force-pushed the convert-3.x-escn branch 5 times, most recently from 90d7e10 to 9134cee Compare October 2, 2024 15:07
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.

5 participants