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

OpenGL: Shader compilation stutter when rendering a 2D element for the first time #76241

Open
metajarra opened this issue Apr 19, 2023 · 8 comments

Comments

@metajarra
Copy link

metajarra commented Apr 19, 2023

Godot version

4.0.2.stable.mono.official (7a0977c)

System information

Windows 11, Compatability, Intel - Intel(R) Iris(R) Xe Graphics

Issue description

I'm using Godot Mono version v4.0.2.stable.mono.official.7a0977ce2

When instantiating a Control node from a PackedScene for the first time, there is a noticeable lag and drop in framerates (60fps to 23fps on my computer). After the first instantiation, this issue no longer exists.

This only occurs in the Compatibility renderer and not in the Mobile/Forward+ renderers. This was tested on a friend's computer using Windows 10 and the same issue occurred in Compatibility only. This was also tested using standard Godot (rather than Godot Mono and this issue also occurred in Compatibility only.

I tested this using Buttons and Labels so I can only confirm that this exists for these Control nodes, however it's possible it occurs for other Control nodes as well.

Steps to reproduce

The project is trivial so a reproduction project is not included.

I used the following code:

// C#
using Godot;
using System;

public partial class instantiation_lag_demo : HBoxContainer
{
	private PackedScene buttonPrefab;
	
	public override void _Ready(){
		buttonPrefab = GD.Load<PackedScene>("res://button.tscn");
	}
	
	public override void _Process(double delta){
		if (Input.IsActionJustPressed("click")){
			Button button = (Button)buttonPrefab.Instantiate();
			AddChild(button);
		}
	}
}
#GDScript
extends HBoxContainer
@onready var button_scene = preload("res://button.tscn")

# Called when the node enters the scene tree for the first time.
func _ready():
	pass # Replace with function body.


# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
	if Input.is_action_just_pressed("click"):
		var new_guy = button_scene.instantiate()
		add_child(new_guy)

This script extends HBoxContainer and is attached to an HBoxContainer in the scene tree. It loads a PackedScene "button.tscn", a scene made up of a single Button with Layout/Container Sizing/Horizontal Expand set to true (this issue is also reproduced if this is set to false).

A single input action is setup called "click" which is triggered when the left mouse button is pressed.

To observe this issue, run the project and click once. The first button will take some time to load and produce a noticeable drop in framerates - clicking again will create additional buttons which load in far less time (ie. no lag, no noticeable drops in framerates).

Minimal reproduction project

N/A

@Calinou
Copy link
Member

Calinou commented Apr 19, 2023

This may be due to shader compilation stutter (to draw the button texture).

Can you reproduce this in GDScript?

@metajarra
Copy link
Author

This may be due to shader compilation stutter (to draw the button texture).

Can you reproduce this in GDScript?

Yes it's reproducible in GDScript using the following code:

#GDScript
extends HBoxContainer
@onready var button_scene = preload("res://button.tscn")

# Called when the node enters the scene tree for the first time.
func _ready():
	pass # Replace with function body.


# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
	if Input.is_action_just_pressed("click"):
		var new_guy = button_scene.instantiate()
		add_child(new_guy)

This causes the same error in both standard (ie. non-.NET) Godot and in my install of Godot Mono

@Calinou
Copy link
Member

Calinou commented Apr 19, 2023

Can you reproduce this issue if you add a Button node in the main scene (so that it's visible as soon as you run the project)? I guess this is an issue that most people don't notice because they have at least one 2D element visible on the first place (either a label or button).

@metajarra
Copy link
Author

Can you reproduce this issue if you add a Button node in the main scene (so that it's visible as soon as you run the project)? I guess this is an issue that most people don't notice because they have at least one 2D element visible on the first place (either a label or button).

Ok testing with a Button already being visible seems to solve the issue. Please let me know if there's any other known solution since this seems janky

@Calinou
Copy link
Member

Calinou commented Apr 19, 2023

Please let me know if there's any other known solution since this seems janky

Reduce the button's opacity to a very low but non-zero value using the Modulate property, and use queue_free() on that button after one frame has passed:

extends Button

func _process(delta):
	call_deferred("queue_free")

You can also scale down the node significantly or put it behind other 2D nodes (such as a ColorRect), but again, the scale should be greater than 0.

Regarding jank: Both 2D and 3D games often end up having to do this to minimize shader compilation stutter during gameplay, even AAA productions. It's a hard problem to fully resolve, as we can't predict all shader combinations that a project might use in advance.

Work is being done on adding shader caching to 4.x's Compatibility rendering method in #76092, which will alleviate this issue after starting the project once. It may also be possible to force some basic 2D shaders to always compile in a separate pull request.

@Calinou Calinou changed the title (Standard, Mono) Instantiating Control PackedScene for first time causes lag OpenGL: Shader compilation stutter when rendering a 2D element for the first time Apr 19, 2023
@Calinou Calinou added this to the 4.x milestone Apr 19, 2023
@metajarra
Copy link
Author

Both 2D and 3D games often end up having to do this to minimize shader compilation stutter during gameplay, even AAA productions. It's a hard problem to fully resolve, as we can't predict all shader combinations that a project might use in advance.

Work is being done on adding shader caching to 4.x's Compatibility rendering method in #76092, which will alleviate this issue after starting the project once. It may also be possible to force some basic 2D shaders to always compile in a separate pull request

Great to know, thank you! I appreciate your help

@clayjohn
Copy link
Member

It would be helpful to know if this issue still exists in Godot 4.1. We added shader caching, so the issue should be mostly resolved if the issue is caused by shader compilation stutter

@djrain
Copy link

djrain commented Sep 22, 2023

It would be helpful to know if this issue still exists in Godot 4.1. We added shader caching, so the issue should be mostly resolved if the issue is caused by shader compilation stutter

Encountering a similar problem in 4.1.1 - tried switching our game from Mobile to Compatibility, and suddenly instantiating / showing things for the first time causes massive stutters on our Pixel 4. I can't seem reproduce the issue with buttons or labels, but loading 10 unique custom shaders on Mobile mode is instant, whereas on Compat it took an entire 5 seconds. I do have shader cache enabled in settings. That is very odd, no?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants