Macros vs Function?

I am really confused with macros vs functions. Consider the following examples:

//Macros
#define MY_MACROS(a) a.value= 1;

//Function
inline void MyFunction(inout float value){ value = 1; }

They both do the exact same thing. So my question is when do you use macros and when do you use functions?

The difference between a macro and a function comes down to scope and the ability to redefine a macro if needed.

A function is fairly straightforward, the values you can access inside a function are limited to those passed into the function or those defined as uniforms (aka the variables defined outside of any function, like material properties or shader globals).

struct v2f {
    float4 pos : SV_Position;
    float myValue : TEXCOORD0;
};

float _MyMaterialProperty;

void MyFunction(inout float value)
{
    // this works
    value *= _MyMaterialProperty * 2.0;

    // error, no foo defined
    // value *= foo;

    // error, no i defined
    // value *= i.myValue;
}

half4 frag (v2f i) : SV_Target
{
    float foo = 5.0;
    float bar = 2.0;
    MyFunction(bar);
    return float4(bar,bar,bar,1.0);
}

A macro on the other hand is code that’s injected directly into the spot it’s called, there for it has access to values that exist at that scope.

struct v2f {
    float4 pos : SV_Position;
    float myValue : TEXCOORD0;
};

float _MyMaterialProperty;

// totally fine since i exists in the frag function before this macro is called
#define MY_MACRO(value) value *= i.myValue

}

half4 frag (v2f i) : SV_Target
{
    float bar = 2.0;
    MY_MACRO(bar);
    return half4(bar,bar,bar,1.0);
}

Basically, a macro is nearly the same as copy and pasting that code into where it’s called, doing a string replacement for any input parameters.

// this
#define MY_MACRO(value) value *= 2
float var = 1.0;
MY_MACRO(var);

// turns into this
float var = 1.0;
var *= 2;

You might notice that some built in Unity macros have a ; after them and some don’t. Generally the old ones have the semi-colon in the macro definition itself, which means you shouldn’t add one when calling the macro because it’ll put two semi-colons in the final shader. Where newer macros don’t have a semi-colon in the macro definition which makes the rest of the code a bit cleaner and more consistent. Neither is more “correct” than the other, but not having a semi-colon in the macro definition is probably preferable.

Also you could have a macro defined in an included file and override it in the shader if need be, which is something you can’t do with a function.

// in myStuff.cginc
#define MY_MACRO(a) a = 1.0

// in your shader file
#include "myStuff.cginc"

#undef MY_MACRO
#define MY_MACRO(a) a = 2.0

half4 frag(v2f i) : SV_Target
{
    float value = 0.0;

    // returns 2.0, not 1.0 for value
    MY_MACRO(value);

    return half4(value, value, value, 1.0);
}
2 Likes

Thank you very much!! This is thoroughly explained!