Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Question Switch samplerstate in custom function

Discussion in 'Shader Graph' started by cp-, Jul 31, 2023.

  1. cp-

    cp-

    Joined:
    Mar 29, 2018
    Posts:
    77
    Hi @FredMoreau, tagging you because of your reply in this other thread.

    The line you suggested uses the texture's samplerstate (as set in import options), right?

    I'm currently trying to replicate the behaviour of the built-in SampleTexture2D node: Use a given samplerstate if present, otherwise fall back to the texture's one. In the c# code for the node the shader code gets emitted based on the state of the input property.

    I wonder how we could achieve the same thing with a custom function without using dynamic branching in the shader itself?

    Edit: Added link to the node's relevant source code line.
     
  2. FredMoreau

    FredMoreau

    Unity Technologies

    Joined:
    May 27, 2019
    Posts:
    108
    Hi @cp-,

    it appears that if you don't provide a SamplerState, it'll default to using the texture's.

    So you may just do it this way:

    Code (CSharp):
    1. void TextureSampler_float(UnityTexture2D _Tex, float2 uv, UnitySamplerState samplerState, out float4 Out)
    2. {
    3.         Out = SAMPLE_TEXTURE2D(_Tex, samplerState, uv);
    4. }
    I'll update you if/when I learn more about this.
     
  3. cp-

    cp-

    Joined:
    Mar 29, 2018
    Posts:
    77
    Hey thanks for your input!

    Thought so too and it works when not binding the Custom Function node's sampler property to anything. This would work on the graph's top level and would result in following behaviour:
    Bind the property to a specific sampler and it will be used OR do not bind the property and the texture's sampler will be used. Great! :)

    But my use case is a bit different, should have mentioned it: My Custom Function lives inside a SubGraph. If I expose the sampler property on the SubGraph's blackboard and bind it to the Custom Function's sampler property it will never use the texture's sampler:
    Bind the property to a specific sampler and it will be used OR do not bind the property and the blackboard's property's default sampler will be used. Not great! :(

    I know it's kind of specific but I wonder if there is a way. I guess if we could set a "null value" as default for the blackboard property it would work. But as of now we always have to initialize the sampler with "non-null" so to speak.
     
  4. cp-

    cp-

    Joined:
    Mar 29, 2018
    Posts:
    77
    Oh I just stumbled upon the "Branch On Input Connection" node that works in conjunction with "Use custom binding" on the blackboard property.

    This could in theory work if the "Branch On Input Connection" node would accept a sampler state as input but it does not. We can only plug in scalar and vector values there. (Same as with the regular Branch node.)

    Edit: Added link to docs.
     
  5. FredMoreau

    FredMoreau

    Unity Technologies

    Joined:
    May 27, 2019
    Posts:
    108
    I actually tried this in a Sub Graph myself, but had not realized that if you don't provide a SamplerState, it'll default to the SamplerState property default value, and not the Texture's.

    I tried a few other things and asked our devs.
    Pure static branching (outputting a subset of a Custom Function in Shader code based on Input Connection State, is not possible.
    But..

    You may use a Property Connection State input on the Custom Function Node. This provide a boolean to the function that you can use for dynamic branching.
    I'm said that it will have optimal performance if the input value is a constant literal or resolves to one.

    texture sampler subgraph.png

    Code (CSharp):
    1. void TextureSampler_float(UnityTexture2D _Tex, float2 uv, UnitySamplerState samplerState, bool samplerStateConnected, out float4 Out)
    2. {
    3.     if (samplerStateConnected == true)
    4.     {
    5.         Out = SAMPLE_TEXTURE2D(_Tex, samplerState, uv);
    6.     }
    7.     else
    8.     {
    9.         Out = SAMPLE_TEXTURE2D(_Tex, _Tex.samplerstate, uv);
    10.     }
    11. }
    12. void TextureSampler_half(UnityTexture2D _Tex, float2 uv, UnitySamplerState samplerState, bool samplerStateConnected, out half4 Out)
    13. {
    14.     if (samplerStateConnected == true)
    15.     {
    16.         Out = SAMPLE_TEXTURE2D(_Tex, samplerState, uv);
    17.     }
    18.     else
    19.     {
    20.         Out = SAMPLE_TEXTURE2D(_Tex, _Tex.samplerstate, uv);
    21.     }
    22. }
    And as you can see here in a main graph using the sub-graph, it'll work.

    Screenshot 2023-08-02 at 9.52.07 AM.png

    You may also provide both implementations with two Custom Function nodes, and use a Branch On Input Connection node to switch between the two.

    static branching.png

    Static Branching of Custom Function code remains an interesting feature.
    Should you want to provide further feedback, I added it to our Product Roadmap here.
     
  6. cp-

    cp-

    Joined:
    Mar 29, 2018
    Posts:
    77
    First of all thanks for the thorough investigation. I have some follow-up questions, please bear with me :)

    Property Connection State

    The Property Connection State is an interesting feature, I completely overlooked that option in the dropdown.
    But if this input is ultimately bound to a main graph blackboard property, it'll stay dynamic?

    Branch On Input Connection

    I actually went down the Branch On Input Connection road, very similar to what you propose in your last screenshot.
    To clarify: Using this node always results in dynamic branching and so both sides will be evaluated (two texture taps in your example case), right?

    My use-case is a bit more involved (and is out of the scope of just switching samplers): I want to define a subgraph node which switches between different implementations based on if there is a secondary texture or another property set. Using the Branch On Input Connection, there are 3 branches right now, with each having 3-6 texture taps. I'm guessing to have 12 taps right now which is not really optimal :) Only one branch would need to be executed.
    And I'd also want to switch on sampler state so with this approach I'd end up with 6 branches and 24 taps, and if I also want to switch on the secondary texture's sampler state... grah.

    I will have to refactor this quite a bit and do some performance testing but I think there is no simple way to have this all neatly packed up in a single node while maintaining good performance.

    Feedback

    Great you placed the feature on the roadmap!

    I think what really would help is to define keywords locally for the scope a Custom Function node or an instance of a subgraph. The keywords could be based on the connection state but also the usual options we have on main graph level (bools, enums). So each compilation of the subgraph could end up in a completely different shader code block and could not readily be reused between all subgraph instances.

    I know there are already subgraph keywords but they can be defined on main level only so all subgraph instances would affected in the same way, right?
     
  7. FredMoreau

    FredMoreau

    Unity Technologies

    Joined:
    May 27, 2019
    Posts:
    108
    Correct. The documentation actually states that "Static branching is available only in hand-coded shaders. You cannot create static branches in Shader Graph."

    Yes, keywords are great for making shader variants, but useless to create a custom node with different behaviors and shader code output based on its settings. That was possible with the API with versions earlier than 2019.x IIRC, but removed since.

    I reframed the feature to make it more generic as it should allow static branching in Main Graphs, Sub Graphs as well as Custom Function nodes.
    This could be used in many ways, such as making custom functions and sub-graphs with static settings, and be able to switch between different implementations of a graph to preview their impact on quality and performance.

    Thanks for the detailed feedback. This really helps framing the scope of required improvements better!
     
  8. cp-

    cp-

    Joined:
    Mar 29, 2018
    Posts:
    77
    Thanks for the lively exchange! I wish I could say that more often here on the forums :)

    Not to be nitpicky but by using keywords and the accompanying nodes there will be variants created which are the same thing as using #if directives in manually shaders?

    By the way, I wish there was a visual way in the graph editor to preview which branches get eliminated based on which keywords are set. Looking at the generated code is too hard to decipher (and clicking "Compile and show code" takes ages in my use-case, I've stopped at >30min on a beefy rig).
     
  9. FredMoreau

    FredMoreau

    Unity Technologies

    Joined:
    May 27, 2019
    Posts:
    108
    Yes and no.
    There's a limit to the number of Keywords, as they will generate variants automatically.
    Using a compile define symbol allows to bypass variants management create static branches based on other things, such as built-in macros.

    Also, like you pointed out, Keywords are set for the whole shader and cannot differ between instances of sub graphs.
    So for that use case, we might need to come up with a solution that doesn't rely on compiler directives and rather switches the Shader code output like in the built-in nodes.