PDA

View Full Version : ????? generalized textures and texture-units ?????



bootstrap
07-29-2017, 04:37 AM
I've been developing my 3D engine for some time, but up until now I've only had lame/simple support for texture-maps and surface-maps (AKA normal-maps). I always intended to "generalize" my texture-access and for that purpose have four u08 fields in every vertex.

My original plan was to put every last texturemap, surfacemap, conemap, heightmap, specularmap, anyothermap, cubemap into an OpenGL "array texture" of the appropriate shape, size and configuration... and have each vertex specify up to 4 of these for shader code.

What this was supposed to means is approximately this:

Current GPUs support at least 32 texture-units.

My vertices have four u08 fields in them to control up to 4 image/texture like thingies (or for other purposes in procedural shaders).

So I figured the upper 3-bits of each of these fields would specify a texture-unit, and the lower 5-bits of each of these fields would specify the layer inside the array texture attached to that texture-unit.

This would let each vertex specify ANY of hundreds of image/texture like thingies for each of up to four purposes. Therefore, in a simple shader, the four u08 fields might specify the image/texture thinkie for:

- texture-map (apply this color to pixel).
- surface-map (tweak normal vector to fake surface structure as in "bump-mapping").
- specular-map (modify specular power for each fragment to simulate surface roughness/quality/etc).
- relaxedcone-map + height-map (implement fancy and efficient parallax mapping).

Of course different shaders could interpret those four u08 fields in different ways (as expected by the objects it renders).

Anyway, that's what my misguided, deluded brain thought I could do, until...

----------

When I started to write the code, I found that indeed I could make the lower bits in those four u08 fields select which layer to access in array textures...

BUT... I could not find a way to make the upper bits in those four u08 fields specify "which texture-unit".

!!!!! UH OH !!!!!

The more I looked at the OpenGL and GLSL v4.50 specifications, and read the latest OpenGL Superbible, the more I came to think it is impossible for vertices to specify which texture-unit its data should be read from. In fact, the following seemed to be the best "standard way" to write fragment shader code in GLSL:



layout (binding = 0) uniform sampler2DArray tu00;
layout (binding = 1) uniform sampler2DArray tu01;
layout (binding = 2) uniform sampler2DArray tu02;
layout (binding = 3) uniform sampler2DArray tu03;
...
layout (binding = 29) uniform sampler2DArray tu29;
layout (binding = 30) uniform sampler2DArray tu30;
layout (binding = 31) uniform sampler2DArray tu31;


... where the number assigned to "binding" is the texture-unit.

But... but... if the texture-unit is specified in the GLSL code, then obviously it can't be specified by 5-bit integer in one of those four u08 fields in each vertex!

Aarg!

I mean, I suppose one could write GLSL code like the following to get the four desired vec4 values from the four desired imagemaps/texturemaps/surfacemaps/etc:



float layer0 = bitfield0 & 3; // extract array texture layer 0 to 7
float layer1 = bitfield1 & 3; // extract array texture layer 0 to 7
float layer2 = bitfield2 & 3; // extract array texture layer 0 to 7
float layer3 = bitfield3 & 3; // extract array texture layer 0 to 7

int tx0 = (bitfield0 >> 3) & 31; // extract value from texture-unit number 0 to 31
int tx1 = (bitfield1 >> 3) & 31; // extract value from texture-unit number 0 to 31
int tx2 = (bitfield2 >> 3) & 31; // extract value from texture-unit number 0 to 31
int tx3 = (bitfield3 >> 3) & 31; // extract value from texture-unit number 0 to 31

vec4 value0, value1, value2, value3; // value.rgba or value.xyzw from specified image/texture/etc

switch (tx0) {
case : value0 = texture (tu00, vec3(tcoord,xy, layer0)); break;
case : value0 = texture (tu01, vec3(tcoord,xy, layer0)); break;
case : value0 = texture (tu02, vec3(tcoord,xy, layer0)); break;
case : value0 = texture (tu03, vec3(tcoord,xy, layer0)); break;
case : value0 = texture (tu04, vec3(tcoord,xy, layer0)); break;
case : value0 = texture (tu05, vec3(tcoord,xy, layer0)); break;
case : value0 = texture (tu06, vec3(tcoord,xy, layer0)); break;
case : value0 = texture (tu07, vec3(tcoord,xy, layer0)); break;
case : value0 = texture (tu08, vec3(tcoord,xy, layer0)); break;
case : value0 = texture (tu09, vec3(tcoord,xy, layer0)); break;
case : value0 = texture (tu10, vec3(tcoord,xy, layer0)); break;
case : value0 = texture (tu11, vec3(tcoord,xy, layer0)); break;
case : value0 = texture (tu12, vec3(tcoord,xy, layer0)); break;
case : value0 = texture (tu13, vec3(tcoord,xy, layer0)); break;
case : value0 = texture (tu14, vec3(tcoord,xy, layer0)); break;
case : value0 = texture (tu15, vec3(tcoord,xy, layer0)); break;
case : value0 = texture (tu16, vec3(tcoord,xy, layer0)); break;
case : value0 = texture (tu17, vec3(tcoord,xy, layer0)); break;
case : value0 = texture (tu18, vec3(tcoord,xy, layer0)); break;
case : value0 = texture (tu19, vec3(tcoord,xy, layer0)); break;
case : value0 = texture (tu20, vec3(tcoord,xy, layer0)); break;
case : value0 = texture (tu21, vec3(tcoord,xy, layer0)); break;
case : value0 = texture (tu22, vec3(tcoord,xy, layer0)); break;
case : value0 = texture (tu23, vec3(tcoord,xy, layer0)); break;
case : value0 = texture (tu24, vec3(tcoord,xy, layer0)); break;
case : value0 = texture (tu25, vec3(tcoord,xy, layer0)); break;
case : value0 = texture (tu26, vec3(tcoord,xy, layer0)); break;
case : value0 = texture (tu27, vec3(tcoord,xy, layer0)); break;
case : value0 = texture (tu28, vec3(tcoord,xy, layer0)); break;
case : value0 = texture (tu29, vec3(tcoord,xy, layer0)); break;
case : value0 = texture (tu30, vec3(tcoord,xy, layer0)); break;
case : value0 = texture (tu31, vec3(tcoord,xy, layer0)); break;
}

switch (tx1) {
case : value1 = texture (tu00, vec3(tcoord,xy, layer1)); break;
case : value1 = texture (tu01, vec3(tcoord,xy, layer1)); break;
case : value1 = texture (tu02, vec3(tcoord,xy, layer1)); break;
case : value1 = texture (tu03, vec3(tcoord,xy, layer1)); break;
case : value1 = texture (tu04, vec3(tcoord,xy, layer1)); break;
case : value1 = texture (tu05, vec3(tcoord,xy, layer1)); break;
case : value1 = texture (tu06, vec3(tcoord,xy, layer1)); break;
case : value1 = texture (tu07, vec3(tcoord,xy, layer1)); break;
case : value1 = texture (tu08, vec3(tcoord,xy, layer1)); break;
case : value1 = texture (tu09, vec3(tcoord,xy, layer1)); break;
case : value1 = texture (tu10, vec3(tcoord,xy, layer1)); break;
case : value1 = texture (tu11, vec3(tcoord,xy, layer1)); break;
case : value1 = texture (tu12, vec3(tcoord,xy, layer1)); break;
case : value1 = texture (tu13, vec3(tcoord,xy, layer1)); break;
case : value1 = texture (tu14, vec3(tcoord,xy, layer1)); break;
case : value1 = texture (tu15, vec3(tcoord,xy, layer1)); break;
case : value1 = texture (tu16, vec3(tcoord,xy, layer1)); break;
case : value1 = texture (tu17, vec3(tcoord,xy, layer1)); break;
case : value1 = texture (tu18, vec3(tcoord,xy, layer1)); break;
case : value1 = texture (tu19, vec3(tcoord,xy, layer1)); break;
case : value1 = texture (tu20, vec3(tcoord,xy, layer1)); break;
case : value1 = texture (tu21, vec3(tcoord,xy, layer1)); break;
case : value1 = texture (tu22, vec3(tcoord,xy, layer1)); break;
case : value1 = texture (tu23, vec3(tcoord,xy, layer1)); break;
case : value1 = texture (tu24, vec3(tcoord,xy, layer1)); break;
case : value1 = texture (tu25, vec3(tcoord,xy, layer1)); break;
case : value1 = texture (tu26, vec3(tcoord,xy, layer1)); break;
case : value1 = texture (tu27, vec3(tcoord,xy, layer1)); break;
case : value1 = texture (tu28, vec3(tcoord,xy, layer1)); break;
case : value1 = texture (tu29, vec3(tcoord,xy, layer1)); break;
case : value1 = texture (tu30, vec3(tcoord,xy, layer1)); break;
case : value1 = texture (tu31, vec3(tcoord,xy, layer1)); break;
}

switch (tx2) {
case : value2 = texture (tu00, vec3(tcoord,xy, layer2)); break;
case : value2 = texture (tu01, vec3(tcoord,xy, layer2)); break;
case : value2 = texture (tu02, vec3(tcoord,xy, layer2)); break;
case : value2 = texture (tu03, vec3(tcoord,xy, layer2)); break;
case : value2 = texture (tu04, vec3(tcoord,xy, layer2)); break;
case : value2 = texture (tu05, vec3(tcoord,xy, layer2)); break;
case : value2 = texture (tu06, vec3(tcoord,xy, layer2)); break;
case : value2 = texture (tu07, vec3(tcoord,xy, layer2)); break;
case : value2 = texture (tu08, vec3(tcoord,xy, layer2)); break;
case : value2 = texture (tu09, vec3(tcoord,xy, layer2)); break;
case : value2 = texture (tu10, vec3(tcoord,xy, layer2)); break;
case : value2 = texture (tu11, vec3(tcoord,xy, layer2)); break;
case : value2 = texture (tu12, vec3(tcoord,xy, layer2)); break;
case : value2 = texture (tu13, vec3(tcoord,xy, layer2)); break;
case : value2 = texture (tu14, vec3(tcoord,xy, layer2)); break;
case : value2 = texture (tu15, vec3(tcoord,xy, layer2)); break;
case : value2 = texture (tu16, vec3(tcoord,xy, layer2)); break;
case : value2 = texture (tu17, vec3(tcoord,xy, layer2)); break;
case : value2 = texture (tu18, vec3(tcoord,xy, layer2)); break;
case : value2 = texture (tu19, vec3(tcoord,xy, layer2)); break;
case : value2 = texture (tu20, vec3(tcoord,xy, layer2)); break;
case : value2 = texture (tu21, vec3(tcoord,xy, layer2)); break;
case : value2 = texture (tu22, vec3(tcoord,xy, layer2)); break;
case : value2 = texture (tu23, vec3(tcoord,xy, layer2)); break;
case : value2 = texture (tu24, vec3(tcoord,xy, layer2)); break;
case : value2 = texture (tu25, vec3(tcoord,xy, layer2)); break;
case : value2 = texture (tu26, vec3(tcoord,xy, layer2)); break;
case : value2 = texture (tu27, vec3(tcoord,xy, layer2)); break;
case : value2 = texture (tu28, vec3(tcoord,xy, layer2)); break;
case : value2 = texture (tu29, vec3(tcoord,xy, layer2)); break;
case : value2 = texture (tu30, vec3(tcoord,xy, layer2)); break;
case : value2 = texture (tu31, vec3(tcoord,xy, layer2)); break;
}

switch (tx3) {
case : value3 = texture (tu00, vec3(tcoord,xy, layer3)); break;
case : value3 = texture (tu01, vec3(tcoord,xy, layer3)); break;
case : value3 = texture (tu02, vec3(tcoord,xy, layer3)); break;
case : value3 = texture (tu03, vec3(tcoord,xy, layer3)); break;
case : value3 = texture (tu04, vec3(tcoord,xy, layer3)); break;
case : value3 = texture (tu05, vec3(tcoord,xy, layer3)); break;
case : value3 = texture (tu06, vec3(tcoord,xy, layer3)); break;
case : value3 = texture (tu07, vec3(tcoord,xy, layer3)); break;
case : value3 = texture (tu08, vec3(tcoord,xy, layer3)); break;
case : value3 = texture (tu09, vec3(tcoord,xy, layer3)); break;
case : value3 = texture (tu10, vec3(tcoord,xy, layer3)); break;
case : value3 = texture (tu11, vec3(tcoord,xy, layer3)); break;
case : value3 = texture (tu12, vec3(tcoord,xy, layer3)); break;
case : value3 = texture (tu13, vec3(tcoord,xy, layer3)); break;
case : value3 = texture (tu14, vec3(tcoord,xy, layer3)); break;
case : value3 = texture (tu15, vec3(tcoord,xy, layer3)); break;
case : value3 = texture (tu16, vec3(tcoord,xy, layer3)); break;
case : value3 = texture (tu17, vec3(tcoord,xy, layer3)); break;
case : value3 = texture (tu18, vec3(tcoord,xy, layer3)); break;
case : value3 = texture (tu19, vec3(tcoord,xy, layer3)); break;
case : value3 = texture (tu20, vec3(tcoord,xy, layer3)); break;
case : value3 = texture (tu21, vec3(tcoord,xy, layer3)); break;
case : value3 = texture (tu22, vec3(tcoord,xy, layer3)); break;
case : value3 = texture (tu23, vec3(tcoord,xy, layer3)); break;
case : value3 = texture (tu24, vec3(tcoord,xy, layer3)); break;
case : value3 = texture (tu25, vec3(tcoord,xy, layer3)); break;
case : value3 = texture (tu26, vec3(tcoord,xy, layer3)); break;
case : value3 = texture (tu27, vec3(tcoord,xy, layer3)); break;
case : value3 = texture (tu28, vec3(tcoord,xy, layer3)); break;
case : value3 = texture (tu29, vec3(tcoord,xy, layer3)); break;
case : value3 = texture (tu30, vec3(tcoord,xy, layer3)); break;
case : value3 = texture (tu31, vec3(tcoord,xy, layer3)); break;
}


But seriously!!! This way is a terrible nightmare... right?

Unless I misunderstand how shaders work, every fragment shader must execute every line... because they all execute the same code in parallel and lockstep, and thus 31 out of 32 lines in every switch statement would be thrown away!

That's just too insane and too extremely inefficient to even consider... assuming I understand how shaders work.

----------

And so, I have to believe there is another way... but my lame brain just can't see it.

So, OpenGL gurus out there, what did I miss. Feel free to laugh at my stupidity... I'm pretty certain I deserve it.

----------

In case it isn't obvious, the reason I want to do this is to be able to render large numbers of objects with a single draw call. Not only that, but I want different parts of [large/non-trivial] objects to be able to selectively control how they are rendered via those four u08 bitfields by selecting different imagemaps/texturemaps/surfacemaps/conemaps/othermaps (or no imagemaps/texturemaps/surfacemaps/conemaps/othermaps).

I could go into deeper detail about why my 3D engine needs/wants to do this, but that is fairly involved so I won't bother.

Anyway... since it is possible to do what I want with the above revolting code, I have to assume the same is possible in some more compact and efficient way. What is that way?

GClements
07-29-2017, 07:54 AM
You can have uniform variables which are arrays of samplers, e.g. sampler2D[]. However, indices must be dynamically-uniform expressions, which basically means expressions involving only uniforms or flat-qualified inputs to the fragment shader.

Aside from that restriction, this provides more flexibility than using an array texture; unlike the layers of an array texture, individual textures don't have to have the same dimensions, internal format, or sampling (filter/wrap) modes. But they do all need to use the same sampler type (e.g. sampler2D), which means the same "shape" (1D, 2D, 3D, cube map, etc) and same element type (normalised/float, signed integer, unsigned integer).

Dark Photon
07-29-2017, 02:22 PM
BUT... I could not find a way to make the upper bits in those four u08 fields specify "which texture-unit".

!!!!! UH OH !!!!!

The more I looked at the OpenGL and GLSL v4.50 specifications, and read the latest OpenGL Superbible, the more I came to think it is impossible for vertices to specify which texture-unit its data should be read from.

Considering your post, particularly in light of your previous post (and your stated goal of maximizing your batch sizes), I think you really should check out bindless textures.

* Bindless Texture (https://www.khronos.org/opengl/wiki/Bindless_Texture) (GL Wiki)

These let you throw the annoying concept of texture units (and the awkwardness of dealing with them in GLSL) in the trash can.


Unless I misunderstand how shaders work, every fragment shader must execute every line...

This is sort of a side-issue, but...

No. Conceptually, lines (or in general code) in a shader can be skipped.

In reality, either code can literally be skipped (actual branching), or the effect of executing the code can be masked (via predication) so that it just looks like it was skipped.

If some threads executing in lock-step need to execute some code while others don't, then it's executed, but its effect is masked for threads that would have skipped it. If none of the threads executing in lock-step need to execute that code, then you can see this is a candidate for a genuine branch.