Now the next thing I want to do is to get some custom hlsl shaders into the pbs system.
Now what I've done now is to create 'pieces' for the code and variables and all that (vertex\pixel shader), and made sure they are set where they should in the 'pieces' definitions, but I was kinda expecting that I then can create a 'standard' pbs material in code, then add those 'pieces' to that one through c++,and by doing that the PBS is extended with the custom pieces. So that when the material is applied to a mesh it will also include the custom vertex shader code in the pbs shader.

But it seems the 'addpiece' is private and not accessible in the pbs material.

So - am I thinking wrong doing it this way? Do I need a full custom PBS class for doing what I want?
I've been looking at the terra example, but that one has a full custom PBS class, and that feels a bit overkill for what I want to do.

Last edited by hedphelym on Mon Jun 25, 2018 8:53 am, edited 2 times in total.

I had to write the topic from what I remembered, I was looking into it while at work, then I wrote the post when I got home, without access to the code.
It was the 'insertPieces' I was thinking about, I was thinking maybe you could define a piece, then use 'insertpiece' on the new material to add it.

But thanks for the pointer, I will try and do it the way you mention, I'll try it tomorrow.

I now have all the custom hlsl code generated and working with hard-coded parameter values in the shader.

We have several renderables that this material is applied to, and I now want to manipulate on the shader variables for each renderable as we did before in ogre 1.x - from c++ code.

I'm wondering how to go about doing that, is by putting the parameters in the passbuffer, then write a listener as with pbs fog?
Or do I have to override more (datablock) to do this?
If I understand correctly - the 'Renderable->setCustomParameter' does not work with the PBS system?

I'm wondering how to go about doing that, is by putting the parameters in the passbuffer, then write a listener as with pbs fog?
Or do I have to override more (datablock) to do this?

Pass buffer is thought for more "per pass" data, rather than per renderable. Although if you have "groups of renderables" that will use identical values, that may just work.
Pass buffers are const buffers, which have a limit of 64kb of per buffer on most GPUs, that is where the reason comes from.

If I understand correctly - the 'Renderable->setCustomParameter' does not work with the PBS system?

It's not that it doesn't work, but rather that we don't use it.
Internally these custom parameters are kept inside a std::map, which would be costly (too many indirections) if we were to access this map per renderable. We'd be penalizing general usage a lot, for an optional feature. If you 100k objects and only 2 use this custom parameter, we'd penalize all 99.998 other objects.

To workaround this problem, there is Renderable::mCustomParameter which is an 8-bit value that requires no indirections (why 8- bit? because I could sneak it somewhere inside Renderable without increasing the RAM consumption per object). Right now it's only used when OGRE_BUILD_COMPONENT_PLANAR_REFLECTIONS is defined.
The philosophy behind this variable is "anything goes". If you use this value to store something else, it will be your responsibility to ensure it doesn't clash with whatever other component that wants to use it.

Note that you would still have to modify Ogre code to send mCustomParameter. You could submit a PR where a macro can be defined to send this value, independent of OGRE_BUILD_COMPONENT_PLANAR_REFLECTIONS (i.e. "I want mCustomParameter to be sent, so send it. I will be using").
I can also accept a PR that optionally upgrades mCustomParameter to a 32-bit value at build time (rationale is: if you want to pay for the extra bytes per object; that's fine. Just don't penalize the rest of our users)

Nonetheless, if you fully override HlmsPbs::fillBuffersFor and implement your own, you can do whatever you want. You can read Renderable::getCustomParameter and send as many as you want.

Alternatively, as an example, I performed something else. I'm writing a GUI library (WIP right now, I don't want to talk much about it because I don't the community to get hyped about it right now), and I use "HlmsColibri" which derives & overrides functionality from "HlmsUnlit".
That way I can create shaders specifically tailored for my UI widgets, while also using vanilla Unlit for everything else. But text rendering required special shaders, that is different from UI widgets and from regular entities. To identify them, I overloaded calculateHashForPreCreate (which gets called when a Renderable is assigned a new datablock/material) and use the magic numbers "6372" to identify the Renderable as an UI widget, and "6373" for text widgets. I only look for the presence of the key, and I don't care about the value. I could read the value and pass it as a property if I wanted (note: different property values => potentially different shaders if the property's value alters the shader code, and definitely different PSO pointers)
But this works because basically all UI widgets follow a different path from regular Unlit, and all text widgets follow a different path from the other two (so there's a total of 3 paths). This method doesn't scale if you want to have e.g. 100s of different values, as it could generate 100s of different shaders (and then RIP shader compilation times, and RIP runtime performance by having the GPU swap shaders too frequently).

Edit: Re-reading your post, it all boils down on how many different values your custom param(eters) can take. If it's not too many, you could also try to deriving and overloading HlmsPbsDatablock, and send a few more params in MyDerivedFromPbsDatablock::uploadToConstBuffer. If you do that, then you need to have one different material for each parameter the value can take (e.g. just like you need two materials if you want an Item with diffuse = 0.5 and another Item with diffuse = 0..

If the parameter can potentially take e.g. millions of different values, then the best course of action is to overload HlmsPbs::fillBuffersFor, and send that parameter by writing it to currentMappedConstBuffer (or currentMappedTexBuffer if they're too many).

Thank you for the reply, that was very useful to read.
On the current shader I'm working on it's just 4x float4 values that call an be manipulated by sending in float values to manipulate the shader for each renderable. It's about 20 renderables that uses this shader, and they can all be set up differently by adjusting the float values in runtime.

function. So now I know that the renderable currently being processed is the one I'm interested in creating custom hlsl code for.
When I find that renderable I then insert all the pieces and such as mentioned above.
All this works fine.

Now comes the question:

(Keep in mind I have more then 1 renderable that needs custom shader code), I now have pieces for each of the custom shaders,
and I use the 'mcustomparameter' to generate pieces for the correct renderable).

In my custom hlms listener, I now override the 'preparePassBuffer' function,
there I set up the passbuffer by adding in all the parameters and such that's added to the shader on my renderable.
The problem it seems (correct me if I'm wrong) - is that this is done for all renderables.
and inside 'preparePassBuffer' I do not have access (it seems) to the renderable, so I cannot increase the buffer only for that object by checking if it's that specific renderable being prepared.

Same with the 'getpassbuffersize', there I want to increase the return value for buffer size depending on what renderable it's currently processing.

With the fog example it's fine to increase the 'getpassbuffersize' value for all objects - because the fog is added to all of them.

What happens in preparePassBuffer is per-pass, i.e. global for that pass.

You have three options:

Add the data to the buffer and apply it to all objects, like fog. However regular objects will just ignore that data (i.e. not use it)

Same as above, but if you need per-renderable data and it's too much data (that would cause your buffer in mPassBuffer to exceed 64kb limit) but still just a few objects, then create another const/tex buffer (i.e. call mVaoManager->createConstBuffer/createTexBuffer). Bind that buffer in mListener->hlmsTypeChanged for all objects, and have the regular objects just ignore that bound buffer; while your special renderables access the data as myBoundBuffer[drawId]

If it's too much data and too many special renderables, you have to overload fillBuffersFor

In options 1 and 2, if your data is per-renderable, then you would have to iterate through your special objects somehow to fill the buffer inside preparePassBuffer. You don't have direct access to the renderables, so you would communicate it externally. Since we're assuming it's a low number of objects, you could just keep track of all the created special renderables in a vector, and iterate through them in preparePassBuffer. So if you have say 40 special renderables, fill the buffers with the 40 renderables' data every time preparePassBuffer is called. If 39 of those renderables were culled by frustum culling, then that single renderable will only access its own entry, and the other 39 entries will be ignored.
Obviously this won't scale for a large number of objects.