Page 1 of 1

Question about Shader:hasUniform and Shader:getExternVariable

Posted: Mon Mar 27, 2023 7:41 pm
by Bigfoot71
Hi :)

I'm writing a small script to manage transitions between different game screens and I wanted to add a system to send values ​​to shader uniforms from the parameters of a Lua function.

So I need to differentiate if the uniform is a vector or an array, because `:send` takes as parameter an array for vectors and a sequence of values ​​for an array. Here is what I would have liked to do roughly:

Code: Select all

function Transition:start(a, b, c, shader_values, ...)

    --[[ ... ]]--

    if type(shader_values) == "table" then

        for k, v in pairs(shader_values) do
            local u_type = self.shaderTrans:getExternVariable(k)
            self.shaderTrans:send(k, (u_type == "array") and unpack(v) or v)
        end

    end

    --[[ ... ]]--

end
However Shader:getExternVariable is deprecated and has been replaced by Shader:hasUniform which now only returns true or false, how could I do it then, and why was it removed?

Re: Question about Shader:hasUniform and Shader:getExternVariable

Posted: Mon Mar 27, 2023 9:35 pm
by slime
I think I'd either structure the input data such that it doesn't need to branch depending on the shader's declaration (e.g. by having the function implementation always use unpack no matter what), or make multiple versions of the function where the caller can specify what they intend via the function name.

With the branch approach you want, how would you support arrays of vectors?

Re: Question about Shader:hasUniform and Shader:getExternVariable

Posted: Tue Mar 28, 2023 12:46 am
by Bigfoot71
slime wrote: Mon Mar 27, 2023 9:35 pm I think I'd either structure the input data such that it doesn't need to branch depending on the shader's declaration (e.g. by having the function implementation always use unpack no matter what), or make multiple versions of the function where the caller can specify what they intend via the function name.

With the branch approach you want, how would you support arrays of vectors?
This shouldn't be a problem for a vector array (unless I missed a case) however another major problem lies elsewhere, thanks to your advice I could see the problem differently.

For the example here is a test shader:

Code: Select all

local shader = lg.newShader[[

    extern vec4 col1;
    extern vec4 col2;
    extern vec4 col3;

    extern vec4 cols[3];

    vec4 effect(vec4 c, Image t, vec2 tc, vec2 sc) {

        int v = int(mod(sc.x, 6.0));

        if (v==0) return col1;
        if (v==1) return col2;
        if (v==2) return col3;

        return cols[v-3];

    }

]]
With this shader, to always use unpack would require having a table like this and it's not really desirable:

Code: Select all

local values = {

    col1 = { {0,1,1,1} };
    col2 = { {1,0,1,1} };
    col3 = { {1,1,0,1} };

    cols = {
        {0,0,1,1},
        {0,1,0,1},
        {1,0,0,1}
    }

}
So I opted for the option to get all the values ​​that follow a character string in order to send them to the shader (since I'm not using a table I also did a recursive `select` function to get the parameters from N1 to N2):

Code: Select all

local function get_elements(n1, n2, ...)
    if n1 > n2 then
        return
    else
        return select(n1, ...), get_elements(n1 + 1, n2, ...)
    end
end

local function send(shader, ...)

    local n = select("#", ...)

    local key;
    local i = 1

    while i <= n do

        local v = select(i, ...)

        if type(v) == "string" then
            key = v
        else

            local from, to = i, nil
            while i <= n and type(v) ~= "string" do
                to, i = i, i + 1
                v = select(i, ...)
            end

            shader:send(key, get_elements(from, to, ...))

            goto continue

        end

        i = i + 1

        ::continue::

    end

end
The function can be called like this:

Code: Select all

send(shader,

    "col1", {0,1,1,1},
    "col2", {1,0,1,1},
    "col3", {1,1,0,1},

    "cols", {0,0,1,1},
            {0,1,0,1},
            {1,0,0,1}

)
The exercise is actually much less obvious if we send it a table in this form:

Code: Select all

values = {

    col1 = {0,1,1,1};
    col2 = {1,0,1,1};
    col3 = {1,1,0,1};

    cols = {
        {0,0,1,1},
        {0,1,0,1},
        {1,0,0,1}
    }

}
Because it is impossible to guess intuitively if the `col1` corresponds to an array or to a vector without adding other values ​​which will make the table heavier, which in any case will end up being discarded almost immediately.

But it would have been much easier to do if the Shader:getExternVariable method was still available, hence my despair :cry:

So good, I was finally able to do what I wanted but if you have the answer to why this method is no longer available it will relieve my disappointment :?

Thank you anyway for your opinion, I might not have done this tonight otherwise! :awesome:

Here is my complete code if you want to try it on your side:

Code: Select all

local lg = love.graphics

local mask = lg.newCanvas()

local shader = lg.newShader[[

    extern vec4 col1;
    extern vec4 col2;
    extern vec4 col3;

    extern vec4 cols[3];

    vec4 effect(vec4 c, Image t, vec2 tc, vec2 sc) {

        int v = int(mod(sc.x, 48.0))/8;

        if (v==0) return col1;
        if (v==1) return col2;
        if (v==2) return col3;

        return cols[v-3];

    }

]]

local function get_elements(n1, n2, ...)
    if n1 > n2 then
        return
    else
        return select(n1, ...), get_elements(n1 + 1, n2, ...)
    end
end

local function send(shader, ...)

    local n = select("#", ...)

    local key;
    local i = 1

    while i <= n do

        local v = select(i, ...)

        if type(v) == "string" then
            key = v
        else

            local from, to = i, nil
            while i <= n and type(v) ~= "string" do
                to, i = i, i + 1
                v = select(i, ...)
            end

            shader:send(key, get_elements(from, to, ...))

            goto continue

        end

        i = i + 1

        ::continue::

    end

end

send(shader,

    "col1", {0,1,1,1},
    "col2", {1,0,1,1},
    "col3", {1,1,0,1},

    "cols", {0,0,1,1},
            {0,1,0,1},
            {1,0,0,1}

)

function love.draw()
    lg.setShader(shader)
    lg.draw(mask)
    lg.setShader()
end