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

[3.x] Add shader tracking & precomp #80345

Open
wants to merge 1 commit into
base: 3.x
Choose a base branch
from

Conversation

Ansraer
Copy link
Contributor

@Ansraer Ansraer commented Aug 6, 2023

This PR adds a launch flag (--track-shaders) that can be used to generate a list of all used shaders during the playtesting and QA process. In addition functions are provided that make it possible to load these shadderlists from code and use them to prepopulate the devices shader cache.

I should note that this solution is not quite as elegant as I would like it to be, I am basically generating massive text files containing both the shader's code as well as any additional information that is necessary to compile it. Earlier drafts of this PR attempted to be smarter about this and only store the necessary options, while referencing the shader's source files instead and also trying to automatically infer information from the scene files. However, as I came across more and more edge cases it quickly escalated in complexity until it became nigh unmaintainable.
This, admittedly dumber and brute force-y, approach on the other hand works far better than it has any right to. I have tested it on both windows and linux without any problems and even managed to get the lists themselves to be fairly small (they have a lot of duplicated substrings, which means that compressing them turned out to be quite efficient). All the shaderlists for the tps demo come in at less than 40KB.

Using this PR

  1. First run your game with the --track-shaders launch flag.
  2. Add some code to you main menu/starting screen to load the generated shader lists and use them to fill your devices cache:
onready var progress_bar : ProgressBar = get_node("ProgressBar")

func _process(delta):
	if(VisualServer.shader_preload_is_running()):
		# Update some ui component
		progress_bar.value = VisualServer.shader_preload_get_stage()

func _on_ButtonShaderPrecomp_pressed():
	VisualServer.shader_preload_spatial("res://spatial.shaderlist")
	VisualServer.shader_preload_canvas("res://canvas.shaderlist")
	VisualServer.shader_preload_particle("res://particle.shaderlist")
	progress_bar.max_value = VisualServer.shader_preload_get_stage_count()
	VisualServer.shader_preload_start()
  1. Run it whenever you deploy your app on a new device

Performance

On my laptop this PR reduced the initial loading times for new shaders to roughly a third of what it was before during a cold launch. According to my benchmarks my laptop usually spends around 15,14 seconds in total to load and compile all the necessary shaders for the TPS demo. By using this PR I got those times down to around 5,97 seconds.

Note that this PR will not result in any additional performance gains during subsequent runs!

When testing this PR make sure you delete all cached shaders on your device before every launch. This depends a lot on your platform but the usual suspects are godot's built in cache, your driver's cache and maybe your operating system's cache.

A note on 4.x

We will need something similar to this in 4.x. However, I am cautiously optimistic that the shaders are handled better in 4.x (you would not believe how many string operations we are performing across multiple cpp files just to prepare a shader for compilation.) so we might be able to come up with a solution that is more elegant than this.
From what I have gathered while talking to W4 employees at the conference I believe that they plan to implement something like this anyways for console support, so for now I will leave 4.x to them.


This PR was sponsored by Ramatak with 💚

@clayjohn
Copy link
Member

clayjohn commented Aug 7, 2023

CC @RandomShaper

@clayjohn
Copy link
Member

clayjohn commented Aug 7, 2023

Where is the speed up coming from on the initial run? Is it just from moving the compilation time to the main menu instead of doing it on the first frame?

@Ansraer
Copy link
Contributor Author

Ansraer commented Aug 10, 2023

Where is the speed up coming from on the initial run? Is it just from moving the compilation time to the main menu instead of doing it on the first frame?

Pretty much. If you start any project without already having the compiled shaders in your driver cache you spend a significant amount of time getting them ready. The goal of this PR is to move that time to the main menu to reduce the intensity of ingame stutters.

I am currently on vacation to deal with an ongoing family emergency, but I should be available for the next rendering meeting. Would be happy to go into more detail during that.

Error err = fa->_open(p_file_path, FileAccess::READ);
ERR_FAIL_COND_V_MSG(err, err, "ShaderPreLoader cannot open file '" + p_file_path + "'. Check user read permissions & the file location.");

//Load shader code
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
//Load shader code
// Load shader code.

Comment style nitpick

String current_shader_code = "";
for (String line = fa->get_line(); !fa->eof_reached(); line = fa->get_line()) {
if (line.begins_with(SHADER_CODE_MARKER)) {
// Start of a new shader
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// Start of a new shader
// Start of a new shader.

if (line.begins_with(SHADER_CODE_MARKER)) {
// Start of a new shader
if (current_shader_id) {
//store prev shader
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
//store prev shader
// Store previous shader.

}
code_map->insert(current_shader_id, current_shader_code);

//Load shader permutations
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
//Load shader permutations
// Load shader permutations.

Map<uint32_t, String> canvas_shader_code;
Map<uint32_t, String> particle_shader_code;

//Store all used action hashes and actions for a given shader code hash
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
//Store all used action hashes and actions for a given shader code hash
// Store all used action hashes and actions for a given shader code hash.

}

ShaderTrackerGLES3::~ShaderTrackerGLES3() {
FileAccessCompressed *fa_spatial = memnew(FileAccessCompressed);
Copy link
Member

@RandomShaper RandomShaper Aug 23, 2023

Choose a reason for hiding this comment

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

In cases like this you can just declare a stack-lived variable: FileAccessCompressed fa_spatial;.

You can handle its lifetime either by scoping or just reusing the same variable. (Or explicitly closing.)

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