Package requirements in ShaderLab

Hello Unity users!

TL;DR:

In Unity 2021.2.0a17 makes it possible to specify package requirements for SubShaders and Passes directly in ShaderLab. A Pass or a SubShader that does not meet the requirements is excluded from further processing and compilation.

(EDIT 14.05.2021): This functionality is mainly aimed at asset store and tool developers that need to target multiple SRPs.

(EDIT 28.07.2021): 2021.1.17f1 and 2020.3.16f1 and later will also support the PackageRequirements block.

Example code:

Shader "MyShader" {
  SubShader {
    Pass {
      PackageRequirements {
        "com.unity.render-pipelines.universal":"[10.0,10.5.3]"
      }
      ...
    }
    Pass {
      PackageRequirements {
        "com.unity.render-pipelines.universal":"[11.0,11.2.4]"
        "com.my.other.package"
      }
      ...
    }
  }
}

The first pass is compatible with URP 10.0 to 10.5.3 inclusive, the second pass is compatible with URP 11.0 to 11.2.4 and requires "com.my.other.package" to be installed.

There are multiple ways to declare package requirements:

  • β€œβ€: specifies that the SubShader or Pass works with any version of the package.
  • β€œβ€: β€œβ€: specifies that the SubShader or Pass only works with a subset of package versions.
  • β€œβ€: β€œunity=”: specifies that the SubShader or Pass only works with a subset of Unity versions and requires a package with the given name.
  • ”unity”:””: specifies that the SubShader or Pass only works with a subset of Unity versions.

Version restrictions define a set of version ranges. If the installed version of a required package is not inside any of the ranges, the package requirement is not met. Similarly, if a requirement specifies a set of Unity version restrictions, the same applies to the current version of Unity.

Specifying version restrictions

In ShaderLab's package requirements, a version uses the major.minor or major.minor.patch format. If you only use major.minor, Unity uses 0 for the patch. Package versions can also include a -preview or a -preview.n postfix where -preview is equivalent to -preview.0. Preview versions come before non-preview versions, so 1.2.3-preview.4 comes after 1.2.2 but before 1.2.3.

There are multiple ways to specify a version range. Each one provides a different behavior. They are:

  • : includes the version you enter and all versions after that. For example, 1.2.3 includes all versions starting with 1.2.3;
  • []: specifies the exact version. For example, [1.2.3] only includes version 1.2.3;
  • [,]: specifies a range between and . Using square brackets and round brackets causes the range to include or exclude to version respectively. The opening bracket affects and the closing bracket affects . Example: [1.2.3,2.3.4) includes all versions from 1.2.3 to 2.3.3.

You can also specify sets of version ranges for a single package. To create a set of version ranges from individual ranges, use a semicolon as a separator. For example, [2.0,3.4.5];[3.7];4.0 includes versions from 2.0.0 to 3.4.5, version 3.7.0, and version 4.0.0 and above.

When you set the versions for a package, be aware of the following:

  • Versions, version ranges, and sets of version ranges cannot contain any extra characters.
  • Version ranges cannot be empty.
  • Sets of version ranges cannot have intersections.

If the syntax does not adhere to the above, the version restriction is invalid.
If you define package requirements that can never be satisfied, the shader import process fails with an error.

Stay tuned for more updates!

8 Likes

Thanks for sharing, this is really cool!

It looks like the package requirements are AND requirements - what if I have a pass that works for URP and HDRP, should I just set Core as requirement?
What if I have a pass that works for URP and built-in, but not HDRP? Duplicating the pass (or many passes, rather) doesn't sound ideal.

1 Like


That's correct.

This is more about what packages/versions are required for the pass to compile. For example, when you're using an include file from URP, you have a dependency on URP being installed. The same applies when you have a pass that works with URP and builtin.

So is the plan for just growing infinitely more complex shaders rather than actually providing a usable abstraction?

This basically allows you to stuff an HDRP7.x, HDRP10.x, URP7.x, URP10.x (and maybe built in?) shader into one file, but that just means that every release of a breaking change to any SRP makes the amount of passes needed grow. Next year the file will contain all the previous years passes plus HDRP13.x and URP13.x, increasing the total complexity further and making maintenance harder. Unlike Unity, anyone providing shaders to people has to support more than one version of an SRP.

For the majority of shaders, the user either wants to modify the inputs to the lighting equations, or the lighting system itself. Rarely are the inputs to the lighting system changed, and right now these systems are so tightly coupled causing everything to break when either side changes. Simply cramming more and more complexity into the same file doesn't seem like forward motion on fixing this issue.

6 Likes

I'm trying to think of a use case for this that isn't about stuffing multiple SRPs into the same file. Correct me if I'm wrong, but the only case where you need to do this is if the package has an include file you need to use, which would cause an error if the package wasn't there.

Originally I thought ok, maybe there's a custom pass needed for some package, so you want to have that pass in the shader when the package is present. But if you just add a custom pass to a shader, it's not going to be used unless the rendering uses it, so that isn't really a use case.

So I'm back at thinking the only use case for this is stuffing multiple SRPs into a single file again - which is really just a road to INF complexity.

1 Like

The idea is to make it possible to ship one shader that works with multiple pipelines and doesn't fail to compile if a package is missing instead of shipping several shaders separately and having the end user to find the right one.


We'll continue improving the shader area :)

1 Like


This is a little disingenuous, isn't it? This isn't actually shipping "one shader that works with multiple pipelines" but rather "multiple shaders shipped in one file" where the number of shaders shipped within that file is going to balloon over time. Am I missing something?

This seems a bit like trying to slap a bandaid on a bullet hole.

Also, is there a way to specify a pass that isn't reliant upon an SRP? I.e. is there a way to create a multi-shader file that supports BuiltIn, HDRP, and URP at the same time?

4 Likes

That can already be done:

https://github.com/slipster216/ShaderPackager

It reminds me of FBX, where they were like "Here's a universal format for Maya, 3dsMax and Motion builder to read and write too" and inside you find 3 files stuffed in the same file. Technically it's not an invalid statement, but not in any way that's really useful.

Since Built in shaders still compile under SRPs, I think you get this for free- at least until they pull Built in.

2 Likes


Rather multiple SubShaders inside a shader.

Yes, there's nothing preventing you from doing that. The SubShaders are still filtered by tags for the current pipeline.


To some extent.


Umm... what are the exceptions here? What is beyond the 'extent' that you imply?

Some context would help.

Why are you assuming that URP 13.x shaders will be incompatible with URP 10.x shaders? Has that proven to be the case? Are you using private calls from the URP shader libraries?

Because that is the pattern set so far. In fact, URP 7.18 and URP7.21 broke compatibility from each other, and they were like 2 weeks apart and both part of an LTS release.

It's actually more likely that there will be incompatibilities between 10.x, 11.x, 12.x, and 13.x than there won't be. For instance, deferred rendering was added to URP at some point along that line, so you have to add new passes, etc. I use 7.x, 10.x, and 13.x as examples because I only support LTS versions in my products for SRPs, the actual incompatibilities are much more widespread. The only way this stuff won't change is if Unity stops developing or adding features to the pipelines, which is the whole point of advocating for an abstraction layer.

I think there are some advantages and disadvantages to both:

Package references in subshaders
+ Doesn't require packing step (less error prone when distributing)
+- Shaders must share property blocks, fallbacks, etc.. Both a benefit and a detriment.
- 5x to 7x times the shader code in one file for support of 2-3 years of pipelines (or more)
- Shaders know about packages (not bad, necessarily, but seems a bit odd for them to know about packages)
- We now have a matrix of stuff to deal with- passes based on SRP versions, #ifdef's based on SRP versions- trying to keep code shared and concise will be a challenge, since you might have 5 versions of a pass for a given SRP in a single file.

Packer approach
+ Can pack shaders from any tool that creates them
+ Only dealing with one shader at a time
+ Shaders don't have to share any properties, fallbacks, etc
+ Much smaller file to process when importing shaders (no idea how much time is spent here)
- Needs better detection of active SRP instead of installed SRP (can be fixed)
- Would work better as official unity code instead of shipped with every asset
- Requires a new file type

5 Likes

Looks rly bad..


What exactly?


I looked at the link @jbooth_1 provided, and he states it clearly that it doesn't work in certain cases. At the same time, from my point of view, having a built-in possibility to have a single asset (shader) work with multiple SRPs at the same time is beneficial.


Let me go over your points one by one.

That's correct. More than that, we provide error checking.

We seem to talk about different concepts here. A Shader in Unity is an asset that has a specific structure inside. SubShaders and Passes always share properties, fallbacks, and other Shader-level things. The addition of package requirements doesn't change that in any way.

Otherwise you'd have to do that in multiple files. So the amount of code doesn't really change much, and now we provide a possibility to have all of that in one place, instead of being scattered between several files.

Packages are there, and people can ship assets inside. Shaders are special in terms of dependencies - the code is scattered across several files. If your shader and its dependencies are split into multiple packages, and a shader cannot function without a specific package, there's an implicit dependency between the shader asset and this package. With the new syntax you can make the dependency explicit.

Would it be different if it were 5 different files? This makes it possible to keep them all in one place, but doesn't require to do so.


Any tool that creates shaders can use the new syntax.

Again, this is about terminology. One shader at a time - do you mean a SubShader? A Pass? Anyway, I'm not sure it's actually a +.

Shaders still have to share them. You're probably talking about SubShaders, but then one can have multiple .shader assets still.

Yes, the asset is larger, however, the import time will not be affected much. Figuring out package requirements is one of the first things we do.

1 Like

Yeah I have to agree with what has been said so far that this feels like a band aid to the real problem and this is why people are frustrated because as usual Unity (as a whole) misses the point.

To a user making a game, they 'should' never need this feature. They should pick a single Pipeline and support just it. They wont ship a build with multiple pipelines because that is madness and I hope also impossible.

So, this feature is mainly for the asset store/tools developers then. Great, fair enough. But... the problem is that you don't address why this is even needed. From the start of this SRP madness Unity have been frustrating by doing things like labelling SRP as production ready and trying to push users towards them when they don't know any better. Horribly deceptive move. I don't care if its just the marketing department trying to big things up, it should never have happened.

Anyway, this extends that. We are now in the middle of this horrible technical mess and each time we see a small feature like this we can't help but think, are you trying to say this is the new normal? Is this what you present as the solution to this madness?

Without addressing stuff like this from the bat, it shows that Unity is not focused on the bigger picture, or at least it feels like that from the outside.

So today you have given us a new feature which gives us more choice to be cleaner (depending on perspective, I do prefer one file over multiple) whilst sharing code which is of course a good thing. But without sharing how this fits into a bigger picture we will always be frustrated because from our perspective, it feels like Unity is either lost or doesn't care about this mess because it always tries to paint the positive picture.

What would have helped me is: "Here is this new feature. It's not going to solve these big problems and we are still working on them, but this was a fairly quick thing for us to make to help make things easier whilst we are working on X as a long term solution".

Without having a clear and definitive big picture you can share with us, we will always be frustrated seeing a small piece because we don't know how it fits in.

Without you sharing how this fits into a big picture, we don't know that there is one. "continue improving the shader area" is not good enough, its too generic.

For me, admitting that SRP are not production ready and that eventually the plan is for them to change no more than twice a YEAR would be the Start of a long term solution. But I feel stupid even typing that which shows how far away from reality that is (and how deceptive labelling them as production ready more than a year ago was).

Aleksandrk I'm sorry you are bearing the grunt of this. I fully appreciate that big companies are bogged down by bureaucracy, too many cooks etc. etc.

5 Likes


This is correct. It is a small feature mainly aimed at asset store and tools developers to ease their life.
It's not going to solve any other big problems, and is not aimed at doing so.
I suppose I can be more explicit about that and not just say "hey, here's this new functionality, try and guess, what we wanted people to do with it". Point taken :)

Would be pretty easy to solve that use case though, simply by checking the active SRP at import. Just because I'm not currently handling it does not mean it's a flaw in the approach.

Keep in mind that I've likely spent less man hours creating an actual shader abstraction that Unity has spent in meetings talking about not providing one at this point.

I fail to see how that's any different? How can it check the shader's validity if the package isn't installed?

Not quite - I'm packing N shaders into a single file, your system provides 1 shader with N subshaders based on dependencies. So in my case I am talking about shaders.

Kinda a + or - really, depending on your structure. For instance, I distribute Better Lit as a single packed file with all (non-unity) includes inlined into the file- this makes it extremely portable, but means the internal shader code is verbose (this is not a problem for me because it's being generated by Better Shaders).

One potential nice change about the new package dependencies that this raises is the ability to have includes which don't have to be pathed in the project. Because Unity only supports absolute or relative paths, it can be difficult to move shaders around if they have dependencies- but now you can install a package with your includes and access them from anywhere without requiring the user to have a specific organizational structure with their asset folder. For instance, I could switch MicroSplat to use includes for some of the places it inlines shader code right now, because I could depend on the MicroSplat-core package and find them from that. Although I guess I could do this without this feature too..

No, shaders don't have to share properties, but subshaders in a shader do. Lets say I'm making a shader for HDRP/Lit and Built in. In HDRP, it uses some extra textures and properties for some HDRP specific stuff. Since I only have one set of properties, unless I write a custom editor, I end up with a bunch of HDRP only stuff in my editor. With my system, each can have their own properties, and thus each their own interface.

Anyway, most of these points are fairly semantical. However, the discussion did bring up one non-SRP use of the new package dependencies, which I look forward to being able to use one day (I'm actually moving MicroSplat into packages now).

I think the issue here is that it's hard to not see something like this and have a PTSD like response to it. Especially when the obvious use case is to stuff all the render pipelines into one file rather than addressing the core issue. I've personally been told by every person at Unity I've ever talked to, including your CTO, that the SRP issue would be fixed, running on five years now, and this is literally the ONLY feature to be introduced in that time that looks like it's attempting to address it (and again, not addressing the real issue). Meanwhile, I had to write my own surface shader system just so I could make maintaining my existing projects viable and not have my revenue cut to a third.

Last year over 60% of my development time was spent on managing SRP shader issues, so imagine if your boss walked into your office and said "Hey, I'm going to cut your pay by 1/3rd, and to make it back you have to spent 60% of your time working on someone else's mess to hopefully earn it back instead of working on actual product ", and then the marketing department put out press releases every week saying how great it was, and then the actual developers you needed to get information from to do this job decided that they weren't going to document any of the code you actually needed to understand to do the work, and then decided they wanted to take away your depot access so you couldn't see changes they made. Then they decide that your a pariah for being upset about it, and tell you to "calm down". I could go on, but it just raises my blood pressure at this point, but if your actually wondering why this is such a hot button issue, this is it.

4 Likes

As a point of reference - the initial development time for Better Shaders was about 2 weeks. There's obviously been plenty of work done in addition to that, and tons of development time before that abstracting MicroSplat to use the the SRPs, but this is mostly reverse engineering HDRP/URP and figuring out how to template that, something that Unity doesn't need to do since they wrote them in the first place.

Writing a text based shader abstraction is not rocket science, nor difficult. It's pretty obvious that Unity has repeatedly chosen to not solve this issue from some kind of stance, not from the actual cost of doing so. The reasons against doing this I've been given do not make any sense (One day we'll have mesh shaders!). Any changes to that abstraction layer for future changes in technology pale in comparison to what we are dealing with right now.

Further, I think Better Shaders has not only proven that these things can be abstracted, it's proven that there is a much nicer workflow for shader authoring than what currently exists in Unity and other engines. Being able to stack functionality from multiple shaders together makes integration of shader based assets trivial, allowing users to combine different solutions from different sources easily. Being able to write new lighting templates and mix and match them with existing shaders is super powerful, opening up new potential markets for stylized rendering that just works with your existing shaders. Being able to have one set of functionality written in text, while another is written in a graph, allows artists and programmers to work together in ways that haven't been possible before. Having something like this standard in Unity just makes sense.

5 Likes


Yes, you're right. I mixed it up in the text, it's the other way around :)
Still, you cannot have a single shader file and have several property blocks in there. You can create several shader assets in this case.


I'm glad the feature is useful after all :)


No, I'm not :) I'm fully aware of the issue you're talking about.


I fully agree with that.

1 Like