Splitting hlsl files between vs and ps

Anything and everything that's related to OGRE or the wider graphics field that doesn't fit into the other forums.
Post Reply
rpgplayerrobin
Gnoll
Posts: 619
Joined: Wed Mar 18, 2009 3:03 am
x 353

Splitting hlsl files between vs and ps

Post by rpgplayerrobin »

Hey!

In my game, shader compilation may take up to 30 seconds on startup, but only the first time.
After the first load the shaders are instead loaded from cache and it takes barely 4 seconds for the entire game to be loaded.

Each shader has the vertex AND fragment code in the same .hlsl file.
Then I had a thought that stuck with me for a couple of weeks... What if the shaders are instead split in two files, one for vertex and one for fragment?
I thought, would that make the compilation time faster?

Since the compilation must remove variables/code it does not use, it would be logical it would skip that step almost completely if it was two files instead of one.
The only question is, how much time would it save?

So I went to work and took a bloated shader in my game and copied it 50 times to new shader materials with unique names.
Then I compiled them from code by loading them in with a resource group, first the version with 50 vs/fs shaders in the same file and then the second version with 50 vs/fs shader in two different files.
The results are:
Loaded and compiled 50 shaders in combined vs/fs files (so 50 files in total): 31.693871 seconds
Loaded and compiled 50 shaders with vs/fs split between vertex and fragment (so 100 files in total): 31.36227 seconds

I made this experiment 5 times in a row and the times above are the biggest change between them (but the one with split files were always a tiny bit faster).
So in essence, the time it takes for the compliation by splitting shaders is negligible, as it was never more than an increase of 0.33 seconds while compiling 50 shaders, which is at the highest 1.05% reduced compliation speed.

So in short, do NOT care about splitting shaders, as it is almost completely pointless (and less user friendly)! :D

loath
Platinum Sponsor
Platinum Sponsor
Posts: 290
Joined: Tue Jan 17, 2012 5:18 am
x 67

Re: Splitting hlsl files between vs and ps

Post by loath »

you can compile shaders in different threads simultaneously. so splitting up your shaders in two might get you a 15 second load instead of 30. with 6 or 8 threads available you could pull out individual shaders from a queue and probably get the load time down to 4 seconds and then in parallel load everything else (non-shaders) and have your entire launch time be 4 seconds for first or cached runs. (this is what i do although your shaders might be more complicated than mine)

rpgplayerrobin
Gnoll
Posts: 619
Joined: Wed Mar 18, 2009 3:03 am
x 353

Re: Splitting hlsl files between vs and ps

Post by rpgplayerrobin »

I guess this has to do with the Ogre version 1.12.4 and above only ("D3D9/D3D11: allow background shader compilation in prepare()")?
As I am still on an older Ogre version I cannot currently do this I guess?

There are so many things I have to do when upgrading Ogre in my project (also a lot of changes in the engine itself), so I have not yet attempted to even do it.

loath
Platinum Sponsor
Platinum Sponsor
Posts: 290
Joined: Tue Jan 17, 2012 5:18 am
x 67

Re: Splitting hlsl files between vs and ps

Post by loath »

i had to look back at my request to paroj to remember the details. :)

you need 1.12.4+ if you want this to work "out of the box" for dx9 or dx11. on this build you just create / get your shader resource and call resource->prepare() from your worker thread.

prior to 1.12.4 you can still make this work with a simple change to ogre. in the header, make Ogre::HighLevelGpuProgram::loadHighLevel() public instead of protected (or maybe it was private). next, create the shader resource as you do now and call resource->loadHighLevel() from your worker thread.

background details if you care:
viewtopic.php?p=547541#p547541

rpgplayerrobin
Gnoll
Posts: 619
Joined: Wed Mar 18, 2009 3:03 am
x 353

Re: Splitting hlsl files between vs and ps

Post by rpgplayerrobin »

Prior to 1.12.4, do you have some code that shows how to load the resources correctly this way? :mrgreen:

Currently I am using a resource group to load everything, and instead I guess I should use this code to create it?:

Code: Select all

Ogre::ResourceBackgroundQueue &rbq = Ogre::ResourceBackgroundQueue::getSingleton();
rbq.initialiseResourceGroup("MainScene", &loaderListener);
rbq.loadResourceGroup("MainScene");

But the problem there is that all actions are synchronous because of this in OgreResourceBackgroundQueue.cpp:

Code: Select all

#if OGRE_THREAD_SUPPORT == 3 // resource system is not threadsafe
#undef OGRE_THREAD_SUPPORT
#define OGRE_THREAD_SUPPORT 0
#endif

But from your other post (viewtopic.php?p=546609#p546609) it seems you just removed those lines of code to make it work?
However, how do you make sure only the loadHighLevel call is threaded and not the rest?
Are you not using ResourceBackgroundQueue for the entire resource group and then make a thread per shader in resourceCreated instead or something?
And then just calling loadHighLevel in that thread and then wait for all threads to finish before calling resourceGroupManager.loadResourceGroup?

I am not really sure how to fix this! :D But since you managed to do it, there must be some way.

loath
Platinum Sponsor
Platinum Sponsor
Posts: 290
Joined: Tue Jan 17, 2012 5:18 am
x 67

Re: Splitting hlsl files between vs and ps

Post by loath »

i do shader creation manually at runtime like this:

Code: Select all

     
// 1. create your shader by passing in the source and any required parameters auto& gpupm = Ogre::HighLevelGpuProgramManager::getSingleton (); auto program1 = gpupm.createProgram ("foo", sh::constants::group::game, "hlsl", Ogre::GPT_FRAGMENT_PROGRAM); program1->setSource (sh::hlsl::standby::create_fragment_source()); program1->setParameter ("target", "ps_5_0"); program1->setParameter ("entry_point", "main"); // 2. the program is now in an unloaded state assert (program1->isLoaded() == false); // 3. from a worker thread call LoadHighLevel () if it's "public" or "prepare" (if you're on a newer build) std::thread myworker ([&] { // 4. this function calls D3DCompile () underneath which is thread safe / callable from anywhere (even out of process) program1->LoadHighLevel (); // must be "public" }); myworker.join(); program1->load (); // 5. load back on the main thread // 6. setup any shader parameters on program1 (you have to compile first via LoadHighLevel or this will trigger a Ogre::Resource::load() and compile on the main thread)
  • the Ogre::ResourceBackgroundQueue approach won't work without Ogre 1.12.4+. Prior to 1.12.4 the Ogre::GpuProgram's prepare() was a no-op. LoadHighLevel () was called in the Ogre::GpuProgram's load() function. You also probably need OGRE_THREAD_SUPPORT == 3.

  • your idea to use the resource listener sounds promising:

    a) create a std::vector<Ogre::GpuProgramPtr>.
    b) call initialiseResourceGroup for the shader's group. for each call to the listener's resourceCreate () save the Ogre::ResourcePtr / Ogre::GpuProgramPtr into the vector.
    c) after initializeResouceGroup finishes create a std::mutex and several std::threads (equal to the value of std::thread::hardware_concurrency()) stored in a std::vector<std::thread>. each worker thread should acquire the std::mutex, pop out a program pointer, release the std::mutex, and call program->LoadHighLevel (). the worker threads should quit when they see the vector is empty.
    d) call join on all the threads in the std::vector<std::thread>.
    e) now on the main thread apply any shader parameters. (aka program->getDefaultParameters()->setNamedAutoConstant (..)
    f) finally, call resourceGroupManager.loadResourceGroup as you suggested to put them into the loaded state.

paroj
OGRE Team Member
OGRE Team Member
Posts: 1994
Joined: Sun Mar 30, 2014 2:51 pm
x 1074
Contact:

Re: Splitting hlsl files between vs and ps

Post by paroj »

Prior to 1.12.4, do you have some code that shows how to load the resources correctly this way? :mrgreen:

why dont you just upgrade to 1.12.4 (or 1.12.13 for that matter)?

rpgplayerrobin
Gnoll
Posts: 619
Joined: Wed Mar 18, 2009 3:03 am
x 353

Re: Splitting hlsl files between vs and ps

Post by rpgplayerrobin »

loath wrote: Thu Apr 28, 2022 1:16 am

i do shader creation manually at runtime like this:

Code: Select all

     
// 1. create your shader by passing in the source and any required parameters auto& gpupm = Ogre::HighLevelGpuProgramManager::getSingleton (); auto program1 = gpupm.createProgram ("foo", sh::constants::group::game, "hlsl", Ogre::GPT_FRAGMENT_PROGRAM); program1->setSource (sh::hlsl::standby::create_fragment_source()); program1->setParameter ("target", "ps_5_0"); program1->setParameter ("entry_point", "main"); // 2. the program is now in an unloaded state assert (program1->isLoaded() == false); // 3. from a worker thread call LoadHighLevel () if it's "public" or "prepare" (if you're on a newer build) std::thread myworker ([&] { // 4. this function calls D3DCompile () underneath which is thread safe / callable from anywhere (even out of process) program1->LoadHighLevel (); // must be "public" }); myworker.join(); program1->load (); // 5. load back on the main thread // 6. setup any shader parameters on program1 (you have to compile first via LoadHighLevel or this will trigger a Ogre::Resource::load() and compile on the main thread)
  • the Ogre::ResourceBackgroundQueue approach won't work without Ogre 1.12.4+. Prior to 1.12.4 the Ogre::GpuProgram's prepare() was a no-op. LoadHighLevel () was called in the Ogre::GpuProgram's load() function. You also probably need OGRE_THREAD_SUPPORT == 3.

  • your idea to use the resource listener sounds promising:

    a) create a std::vector<Ogre::GpuProgramPtr>.
    b) call initialiseResourceGroup for the shader's group. for each call to the listener's resourceCreate () save the Ogre::ResourcePtr / Ogre::GpuProgramPtr into the vector.
    c) after initializeResouceGroup finishes create a std::mutex and several std::threads (equal to the value of std::thread::hardware_concurrency()) stored in a std::vector<std::thread>. each worker thread should acquire the std::mutex, pop out a program pointer, release the std::mutex, and call program->LoadHighLevel (). the worker threads should quit when they see the vector is empty.
    d) call join on all the threads in the std::vector<std::thread>.
    e) now on the main thread apply any shader parameters. (aka program->getDefaultParameters()->setNamedAutoConstant (..)
    f) finally, call resourceGroupManager.loadResourceGroup as you suggested to put them into the loaded state.

Nice!

Though I think initialiseResourceGroup calls its loadHighLevel function, so even at that stage it must be threaded somehow.

But the manual creation of the shaders sounds pretty promising if the upgrade to 1.12.13 does not work.

rpgplayerrobin
Gnoll
Posts: 619
Joined: Wed Mar 18, 2009 3:03 am
x 353

Re: Splitting hlsl files between vs and ps

Post by rpgplayerrobin »

paroj wrote: Thu Apr 28, 2022 12:20 pm

Prior to 1.12.4, do you have some code that shows how to load the resources correctly this way? :mrgreen:

why dont you just upgrade to 1.12.4 (or 1.12.13 for that matter)?

I am trying to upgrade to 1.12.13 now, we'll see if it works.

I guess the threaded shader compiling system there is automatic somehow?
How can I get it working there? Something like this?

Code: Select all

// Threaded initialize, I guess this works for all resources? Or how do I make it only for shaders if that is the only thing that works?
BackgroundProcessTicket tmpTicket = resourceBackgroundQueue.initialiseResourceGroup(m_groupName);
while (!tmpResourceBackgroundQueue.isProcessComplete(tmpTicket))
{
	Ogre::Root::getSingleton().getWorkQueue()->processResponses();
	Sleep(1);
}

// Synchronous load now that all shaders have been compiled
resourceGroupManager.loadResourceGroup(m_groupName);
loath
Platinum Sponsor
Platinum Sponsor
Posts: 290
Joined: Tue Jan 17, 2012 5:18 am
x 67

Re: Splitting hlsl files between vs and ps

Post by loath »

the photo here implies the resources are created in an unloaded state during initializeResourceGroup ():
https://ogrecave.github.io/ogre/api/lat ... ement.html

here is the code i use to background load non-shaders (but it works for shaders as well):
https://ogrecave.github.io/ogre/api/lat ... ource.html

Post Reply