How do I solve scope problems in Metal Shader Language?

In a complex shader with, for instance, the main fragment shader function calling sub-functions, those sub-functions might need access to data from the input parameters. Or even access to a value computed in the top level function.


Perhaps the function needs a float holding a value I want to compute only once. Or a texture which has useful data.


I am probably missing something, but the rigid scoping of the metal shader language seems to make that more difficult than in other shader languages.

When trying to access this stuff we see "use of undeclared identifier"


Most of the example code has only simple, single-function shaders, so obviously the problem does not present itself.

I tried nesting the sub-functions inside the main fragment shader (to share scope), but that is not allowed.


One solution would be to pass those values as function parameters all the way down. But that looks ugly and I am guessing that it would be inefficient.

I just wondered whether the MSL creators had a solution to this?

Post not yet marked as solved Up vote post of Carniphage Down vote post of Carniphage
1.5k views

Replies

Hi, I don't really understand, what you want to achieve, because MSL acts just like C++. Propagating data needed for calculations or for change inside inner function call is possible via value and reference arguments respectively. You shoudn't really care about efficency in that case, because all functions are inlined when compiled.

Here's an example.


I have a main fragment shader which calls

A raytracer whch calls a...

raymarch function which calls ...

Five different mapping functions .. all of which call a common...

Noise function.


The noise function needs a texture and a sampler.


Only the main fragment shader can see the texture and the sampler.

So, I guess I have to create this big chain of parameter passing. Roughly 31 lines of code needed to be changed, just so that one function can get at a variable it needs.


In C++ I can place variables at the global level. In MSL I cannot.


I can see this is sort of pure. But it makes the code verbose, much more cluttered and way more expensive to make changes to.

I hoped there would be a more elegant solution.

If you need to pass shared state, you need a shared state object (e.g. a struct that describes that state). Speaking abotu C++ (or in fact any other imperative programming language), using global variables for that purpose is bad programming style — for a number of well studied reasons. For instance, it defeatus compiler optimisations and structures your program in a way that makes it less maintainable.

Although I understand how global varibles do pose a risk, the alternatve in this case is an ugly and unreadable mess.


In order for one shared function to access a single parameter, nearly 40 lines of code needed to be changed. This in no way assists readability or maintainability. It has become a spaghetti mess of repetitive parameter passing which obscures intent and clarity. And shoud I need to change or revisit the code, the mess would need undoing. I want to experiment with my shader, this burden is overwhelming.

If you look at other shading languages, the uniforms are scoped so that all functions can access them. And in my opinion, that results in perfectly readable and maintainable code.


But I would be extremely interested in seeing an example of a shared state object. Almost every example of Metal shader code has been trivial single function examples.

There are no non-constexpr global structures, possible in MSL. If you want to play with your code a bit, just make a single structure, initialized in your base function with your samplers/textures/other stuff, and pass just this single argument in all your raytrace/raymarch/what-not/ function. If you want even more space for code-games - make all your subfunctions templated around this extra-parameter, so you can pass different ones in different cases. In that case type-consistency will be compile-time checked only on those functions, that use the structures, so mid-chain functions will pass it without even trying to check, what is in. It's really hard to say, what your are expecting to optimize without seeing your code structure example. Last approach will not affect code speed, because it's forced to be inlined.

I will check out this approach.


It looks like the intention behind MSL was to support richer shader functionality, but pretty much every published example is just a simple function.