Skip to content

Uniform arrays

Steven Johnson edited this page Mar 2, 2022 · 24 revisions

TODO: Most of the uniforms go unused by Corona. Can grab these, but it needs to exist before we can query its location. When we declare it, of course, we need to give it a size, and this is the sticking point. Some suggestions this might be a guessing game, say since Android steals a few slots too?

Motivation: way to supply a lot of data to a shader, but less cumbersome than packing into a texture. Would be expensive to honor the per-primitive uniforms policy, so don't bother and just resync if dirty when shader becomes current. This usage pattern is actually powerful in its own right. (In theory could use buffers on some hardware to share uniforms among shaders, but probably not worth going down that road?) Used it in a couple ways to great effect on a DX9 game to store connected geometry and "skin" it in motion.

This goes roughly like:

graphics.defineEffect{ --[[ stuff ]], usesUniformArray = "vertex" }

local state = graphics.getShaderState("filter.custom.MYSHADER")
local n = state:GetUniformArraySize() -- vectors or maybe components

for i = 1, n do -- don't necessarily know what we'll get (e.g. porting to several platforms), but can often
                -- deal with small 4-element blocks or connected strips
  state:SetValue(GET_A_VALUE()) -- curve components or something structured
end

rect1.fill.effect.index = 1 -- effect will use this to index a uniform
rect2.fill.effect.index = 2

Then in the shader:

P_UV vec4 v = CoronaGetVectorFromArray(CoronaVertexUserData.x);

// Do something

or more interestingly:

P_UV vec4 v1 = CoronaGetVectorFromArray(CoronaVertexUserData.x), // Build a Catmull-Rom segment from four consecutive
                                                                 // points, say; another idea would be to build up
                                                                 // vertex-skinning weights with multiple indices
          v2 = CoronaGetVectorFromArray(CoronaVertexUserData.x + 1.0),
          v3 = CoronaGetVectorFromArray(CoronaVertexUserData.x + 2.0),
          v4 = CoronaGetVectorFromArray(CoronaVertexUserData.x + 3.0);

// Modify geometry

(In the use case I mentioned above I also had a variant where part of the array contained geometry and the rest indices, allowing a crude form of instancing.)

How to populate? Probably table methods as per uniform userdata, but obvious candidate for bytes arguments, too, especially if using say a C++ plugin to generate data anyway.

(This feature along with some of the ideas undergirding custom display objects, I think, are what I was trying to elaborate, a bit clumsily perhaps, in the proposal made here and here. As discussed in those, there are actually possibilities of automating some of the patterns above, perhaps via some special display objects.)

Known bug to be aware of discussed here (see also the links).

Also, I guess accesses with non-constants isn't guaranteed in the fragment shader, there's no real point worrying about that side of it.


Idea for declaration:

Must have something like uniform vec4 u_VectorArray[50] in source if uniforms were requested, else failure.

However, it could be something like ...u_VectorArray[kArraySize]. This could be stripped of spaces and looked up in the source. If that special constant is found, the count is recalculated according to some startup evaluations: the uniforms that's "always" there, plus any uniform userdata, then masks (probably as if always using threes, unless we explicitly opt out?). We then optimistically allocate the widest bin between locations or the end. This will be the count and we cache it and also #define it earlier in the source. (Maybe add some stub lines that can be replaced in this way, or just one more thing to add when building the source.)

Update: This seems to be in at least the basic working stages. Now Corona will give a conservative estimate of the available uniforms and then eagerly allocate them all, or you can opt for a lower count in the effect declaration.

Problems: If the effect is never drawn once some ranges are dirtied, it might never see the update and become unsynchronized. URGGH Observer pattern? Or do a full sync if too old?

Another way to opt in to using uniform arrays would be via a name, thus allowing sharing among effects. This would run into the same problem just mentioned, but an eventual fix is probably no worse. Some lifetime details would differ a little. Importantly, this would immediately pave the way to uniform buffers when supported.

Update #2: It seems like the real way to go is to allow the uniform array to be created externally and passed in to graphics.defineEffect, perhaps still allowing a one-shot one to be made as now. This largely obviates the "state", since the array itself would do the job, and rather elegantly; the remaining roles can probably be serviced by free functions. Provision might still be offered for instantiable effects that never use vectors.

At some point this could be streamlined with the bytes approach?

The lifetime needs to be hammered out a little. The array could also probably be accessed now via the effect, so the aforementioned one-shots would still be assignable.


I consider this high-priority.

UPDATE: This has been implemented (as a plugin / samples) here, in terms of "Custom objects" #3.

The uniform is assigned lazily, rather than trying to couple it up-front.