[SOLVED] Application will stutter in the Intro Animation

Alright, awesome, this helps a lot and thanks for explaining!

I’ll leave this open, if any follow-up questions occur but for now I got something to work with.

As a sanity check, if you disable shadows under Project Settings → Rendering → Clustered Lighting, does the stutter go away? Also are you on Windows?

Yes, disabling shadows completely removes the stutter. There still are minor events in the profiler, but those don’t seem to cause noticeable frame drops.

Yes, I’m on Windows 11. And if that matters - I’m using an RTX 3080

My best guess here is the stutter is due to new lights coming into frame and the renderer updating shadows. I didn’t think it would be that costly though :thinking:

Well maybe these settings affect it too. Reduced the shadow atlas resulution to 1024 and the Shadow type to PCF 3x3 and it reduced the time of the shaders compiling by a third.

image

What I just figured out:

Assigning a preloader script to the scene root, which loads all the assets for one frame and then disables them again, increases the stutter at application start by roughly 50ms. In addition to that though, everything else works fine after that, with a constant frame rate and no issues at all. I may just overlay the stutter with an additional frame of loading screen so it won’t even be noticeable and all my problems are a thing of the past I guess

What do you have in the preloader script?

It is overall the script used in the “Load multiple assets at runtime” demo, only the onAssetsLoaded function is somewhat different. Also I didnt use assets tagged as level but rather my materials tagged as “material”.

var Preloader = pc.createScript('preloader');

// initialize code called once per entity
Preloader.prototype.initialize = function() {
    this.loadAssets();
};

Preloader.prototype.loadAssets = function() {
    var self = this;
    
    // Find all the assets that have been tagged 'level'
    // In this example, they are in the 'assets-to-load' folder
    var assets = this.app.assets.findByTag('material');
    var assetsLoaded = 0;
    var assestTotal = assets.length;
        
    // Callback function when an asset is loaded
    var onAssetReady = function() {
        assetsLoaded += 1;        
        
        // Once we have loaded all the assets
        if (assetsLoaded === assestTotal) {
            self.onAssetsLoaded();
        }        
    };
    
    // Start loading all the assets
    for(var i = 0; i < assets.length; i++) {
        if (assets[i].resource) {
            onAssetReady();
        } else {
            assets[i].ready(onAssetReady);
            this.app.assets.load(assets[i]);
        }
    }
    
    if (!assets.length) {
        this.onAssetsLoaded();
    }    
};

Preloader.prototype.onAssetsLoaded = function() {
    this.app.root.findByName('materialprerender').enabled=true;
    this.app.root.findByName('Gelaende').enabled=true;
    this.app.root.findByName('Schriftzug_Neu').enabled=true;
    setTimeout(function(){
        this.app.root.findByName('materialprerender').enabled=false;
        this.app.root.findByName('Gelaende').enabled=false;
        this.app.root.findByName('Schriftzug_Neu').enabled=false;
    }.bind(this), 1);
};

Ah, you have this where I assume you are rendering the materials in view. That 's the important part as it looks like your material assets in the Editor are all set to preload anyway

Yes, I used tiny cubes with all the materials assigned to them. They just show for the one frame. The other assets I load because they are quite large in geometry and will not cause any performance hit when loaded into the scene at a later time.

image

1 Like

Just thinking that it wouldn’t be too hard to write a script that pops a list of materials on the screen for one frame only :thinking:

Probably could do that on a layer on a camera that renders before the main one so that it gets overdrawn?

Well that might work aswell. Unfortunately I am by no means proficient in JS nor PlayCanvas and this is a big learning experience for me rn, so I don’t really know how to tackle a script like that, especially with switching rendered cameras.

I may look into the demos and examples at a later date and probably come up with some makeshift solution though :smiley:

Okay uargh I’m the kind of person to say “I’ll do it later” but will do it right away anyways :rofl:

Here’s my script I use to render another camera in the first frame and then swapping to my main camera afterwards.

The Prerender-Camera renders the layers world and prerender. All the cubes with the materials are on the layer prerender, which my main camera doesn’t render.

@yaustar Maybe you can take a quick look at the script and let me know if you think that this is an appropriate solution.

var Preloader = pc.createScript('preloader');

// initialize code called once per entity
Preloader.prototype.initialize = function() {
    this.activeCamera = this.entity.findByName('CameraPrerender');
    this.loadAssets();
};

Preloader.prototype.setCamera = function (cameraName) {
    // Disable the currently active camera
    this.activeCamera.enabled = false;

    // Enable the newly specified camera
    this.activeCamera = this.entity.findByName(cameraName);
    this.activeCamera.enabled = true;
};

Preloader.prototype.loadAssets = function() {
    var self = this;
    
    // Find all the assets that have been tagged 'level'
    // In this example, they are in the 'assets-to-load' folder
    var assets = this.app.assets.findByTag('material');
    var assetsLoaded = 0;
    var assestTotal = assets.length;
        
    // Callback function when an asset is loaded
    var onAssetReady = function() {
        assetsLoaded += 1;        
        
        // Once we have loaded all the assets
        if (assetsLoaded === assestTotal) {
            self.onAssetsLoaded();
        }        
    };
    
    // Start loading all the assets
    for(var i = 0; i < assets.length; i++) {
        if (assets[i].resource) {
            onAssetReady();
        } else {
            assets[i].ready(onAssetReady);
            this.app.assets.load(assets[i]);
        }
    }
    
    if (!assets.length) {
        this.onAssetsLoaded();
    }    
};

Preloader.prototype.onAssetsLoaded = function() {
    // Display Materials on a seperate layer
    this.setCamera('CameraPrerender');
    this.app.root.findByName('materialprerender').enabled=true;
    this.app.root.findByName('Gelaende').enabled=true;
    this.app.root.findByName('Schriftzug_Neu').enabled=true;
    setTimeout(function(){
        // Swap to main camera and enjoy =)
        this.setCamera('CameraViewer');
        this.app.root.findByName('materialprerender').enabled=false;
        this.app.root.findByName('Gelaende').enabled=false;
        this.app.root.findByName('Schriftzug_Neu').enabled=false;
    }.bind(this), 1);
};

Also here’s a link to the updated public version so anyone can take a look at it at a later date:

I’m personally thinking about doing something more generic but there’s nothing inherently wrong with your solution.

Well if you’d like, I’d love to hear what you mean by more generic :smiley:

Other than that, I wanted to say thank you again for the help and your time!

The ideal would be to call a function or an event that passes a list of material assets to show on screen. At which point, it would render tiny little dots in the corner/edge of the screen for frame only.

This way, it can be used in a wider variety of apps like open world streaming metaverses or games.

Oh okay, I’ll look into that “later”.
Didn’t know you were able to render material assets themselves.

I’ll update this thread once I’ve figured something out!

Dealing with the shader compilation issue again here. I think there should be an easier way to force compile the shaders for the given set of materials. If we have a specific list of material assets loaded and ready, we should be able to tell the engine “hey, compile the shader using these materials and tell me when you are done”. Its a pretty common issue and using single frame setup seems like a hacky workaround for something that should be part of an API.

1 Like

I spoke with @mvaligursky about this before and can’t remember if he staid there would be some issue with it due to not knowing fully what shader paths are needed until it’s actually in the scene it’s going to be used in.

He can probably comment when he’s back from OOO

2 Likes

It’s complicated. The generated shader (in case of a StandardMaterial) depends on many additional things which impact the way it gets generated, and those are not known ahead of time. An examples here are:

  • vertex format (we generate different shader if mesh has tangents for example) - so we need to know what mesh is used
  • texture formats - some textures might need decoding (RGBM/RGBE/RGBP) and so we need to know what materials is used
  • global lighting settings / envLighting texture format that needs encoding, mesh instance that specifies lightmaps and similar.

We cannot generate the shader till we know all those (and some more). And so we cannot easily do this ahead of time.

There was a functionality that let you play the game, which collects all ‘options’ the shaders use, and this allowed shaders to be precompiled if used. This was never functioning really well and this is temporarily not supported - even though I plan to add support for something similar in the future.

I’ve started splitting the rendering into two passes - first that walks over the primitives and triggers shader compilation - so that all shaders compile in parallel. But so far this is done per render pass, and not globally. I’m planning to make this global at some point (maybe), and at this point we might have an API that only triggers shader compilation, without the rendering. But none of this is simple to do at all.

4 Likes