@bjz Environment structure captures all shader parameters (note: vertex attributes are not parametes, they are inputs, and they are provided by the Mesh). It can be used with multiple shaders, and all it allows all the operations to be one-way (not requiring feedback from the render thread), while providing maximum performance (parameter name is mentioned only once, and is not accessed on the fast rendering path).

@bjz Honestly, I woke up today not being so definite about Environment as I was when the idea hit me. In particular, the fact that the environment has the state (even though it's very limited - only parameter values can change, not their count or types; also, the environment allows shader program to not have the state) makes me think again about how is that better than just having the program with the state. All in all, any suggestions to make it better are welcome!

@bjz I was going to ask you about it, but didn't want to flood the channel before you read my other notes ;) So, what problem does this handle thing solve exactly (i.e. why/where do we need it)? Also, how does it interact with @photexEntityData thing?

@bjz the environment structure is immutable, but you can change the values. E.g. you can use the same environment for the whole render phase of drawing stuff into the shadow map, and per model you'd like to set the uniform buffer (or value) of the model matrix.

brendanzab @bjz wonders if, due to Environments being coarser grained, they would be more amenable to being reference counted

@bjz Please consider the following alternative. create_program() returns a descriptor, in which the user can get variable handles (not add them like with Environment, because program descriptor already knows what program needs). Then the user can just set these values per program handle in the same way it is done now per environment handle: renderer.set_program_uniform(program_handle, uniform_var, value). That would make program state mutable, but it would simplify things quite a bit. In particular, we'd need one less parameter for the draw call. The big downside though is that the user will need to do this for every program he has. So having multiple models of possible different shaders (that share most of the parameters) becomes a huge burden. We can leave Environment at least to get this stuff more convenient... Just something to thing about (for @csherratt as well)

@bjzMark is supposed to be a semi-unique hidden value to protect variable handles to be used on foreign environments. Once the variable handle is generate (by, for example, env.add_uniform()), it has to be used with this environment only, and Mark is how I enforced it.

@bjz Because the user may assign parameter values on it, and the following draw calls may actually depend on them? So, technically, it wouldn't change any mutability modifies on the structs or their fields. It's just the perception of the program by the user - if he has an ability to change program state, then the program is considered mutable.

@bjz On the other hand... having many different programs should be discouraged, so perhaps forcing the user to manage parameters for each program is not such a big deal?.. I wish we could make it less binding though, but I feel that would require the user to pass all the parameters on every draw call, which is a bit too heavy...

Assign program parameters per program, which will have it's mutable state exposed. Cost is low, ergonomics is pretty good. Chance to shoot yourself in the foot (SYF chance) is high.

Have Environment structure to contain all the parameters, it is partially-mutable, and can be used with multiple programs at will (that's what we have now). Cost is pretty low due to shortcut cache, ergonomics is a bit worse (due to the need to care about environment), and the SYF chance is normal (parameter values can still leak if user forgets to set them).

Pass all the parameters with the draw call, thus making the program state immutable from the user perspective. Cost is higher, but depends on the implementation of the parameter descriptor. Cloning Environment would involve 3 heap allocations for the vectors, for example. Ergonomics is the same as in 2, and the SYF chance is low (user is in full control of the parameters)

@bjz Personally, the bindless nature and SYF chance are the most important things for me, in regards to the shader parameter design, so I'd prefer Option-3 to be shaping up. In past, my Environment thing was just a dictionary of string -> uniform, that the program took values from upon binding. As I said in the #19 description, this is not quite what we need here, but at least it was option-3 in a sense.

@bjz Agreed. Well, the good thing is that we at least have some solution, so someone can do the terrain sample in parallel with our future design changes. I've reopened #19 to figure out how to implement Choice-3..

@bjz Another question is, do we want the environment to be bound strictly 1:1 to a program? If we do, then program creation can return an environment struct with all the parameters laid out, so the user only needs to change the values. It would also render the Shortcut irrelevant, and no caching is going to be needed (sounds like all positive signs here...)

brendanzab @bjz wonders if we can get some sort of hash of the shaders to ensure they are not re-compiled

@bjz before I start adding new labels (issue tags)... is there a naming convention you'd like to follow?

@bjz funny thing about this micro-optimization is that using a hash of the shader code brings in a small SYF factor, in case of a hash collision...

@bjz I've noticed that piston labels start with uppercase (like "Easy"), but that is a bit inconsistent with the built-in labels, which are all lower-case... We should either remove the existing ones entirely, or make new ones that follow the suit, unless you explicitly want our custom labels to differ from the built-ins.

@photex You can treat generations outside of the allocated range as 0. So if add needs to access it, it will extend the allocation vector. I'd like to keep it from pre-allocating 2^16 words for each handle type...

removing something unfortunately also invalidates the handle as we have it now

because the index of the handle will not be valid (the item it referred to was part of a swap)

so say we always push when adding, unless generations were preallocated, you couldn't happen to know that you just pushed an item onto a spot that had been used before (aka it's a different generation)

if you remove something, you need to potentially track it's index so that the next add took place in that spot