Array of textures (of different sizes, format, etc)

Hi there!
I have a situation where I’d like to send a number of textures down a shader (for an overlay system) where each texture will have its own different resolution.
What I am hoping to find is something that will allow me to declare an array of textures that I can set from the CPU. Something that will save me from defining a bunch of texture2D and a bunch of ifs (#if texture3 albedo*=texture3.sample sort-of-thing) I don’t need trilinear or anything fancy.

So, playing with shaders I’ve noticed I can get this to compile:

 fixed4 overlay = _Overlay[0].Sample(overlay_point_clamp_sampler, newUV);

(…which, hopefully won’t be related to TextureArrays as those are based on Texture2DArray HLSL type)

Even though I can get that to work (Texture2D[ ] and a sampler) what I can’t do is set the texture from c#, nor find documentation about it. I was hoping it would be some sort of macro somewhere so that I send “overlay_0” and it’d set the texture at the right position in the array, but nothing seems to do the trick.

I use Texture2DArray in other parts of my code, but I am trying to avoid it in here. It isn’t a very nice solution to create a temp TextureArray for this that I’ll also have to juggle with as textures come and go to/from the overlay system.

So, in essence, can I set entries in a HLSL array of textures from c# so I can pick them using an array operator in HLSL?
Maybe it is a Unity built-in enchantment? Maybe as a smart macro?

thanks!

HLSL doesn’t support arrays of texture uniforms*. GLSL doesn’t really either, though will sometimes like you compile shaders with arrays of textures, but in the background they’re being expanded out to individual texture entries. In the background doing something like your above code, if it were to compile, would likely expand out to sampling every texture in the array and then using an if statement to only use the wanted sample.

Your choice is either to use a texture atlas or a Texture2DArray (the later of which could also contain atlases). There isn’t another solution.

  • The asterisk here is both Vulkan and Direct3D 12 support arrays of textures via bindless texturing, but I don’t believe these can be used within Unity yet.
1 Like

Yeah, I was afraid you’d say that. I am aware that bindless does allow that sort of thing (I was hoping for a bit of unity hocus-pocus macro somewhere), but that’ll take a while to make it to shaderlab. Time to wipe up some homegrown stuff :stuck_out_tongue:
Thanks!

Thread necro! Related question. If you go with the Texture2DArray approach, is there a good way of dealing with mipmaps? It’s relatively easy to make a Texture2DArray with the dimension being the max size of the textures you want it to store, and then in the fragment shader have some uv adjustment to properly sample the specific texture. Is there a good way to handle mip maps in this scenario though?

My thoughts on options were:

  1. Do it manually. I.e., Make the Texture2DArray big enough to hold the max size plus its’ mip maps, and reinvent the wheel by manually copying each mip level into a know part of the destination slice in the Texture2DArray figuring out (via either ddx/ddy or something) which mip map to sample, and then doing it myself. I don’t like this option because I think whatever the GPU is doing is probably going to be much faster and better than what I would do.

  2. Maybe with some clever copying it could “just work”. I.e., let’s say I have two textures I want to store in the Texture2DArray, one that is 1024x1024, and one that is 512x512 (thus, the dimensions of the Texture2DArray are 1024x1024). When I go to copy the larger texture into the array, I just do a regular copy. When I go to copy the smaller one, I would copy the first level (the 512x512) with an offset of (0,0) (basically a regular copy, except only copying 512x512). Then, when I go to copy the second mip level, instead of copying it into the “usual” destination for mip level 2 of a 512x512 texture (not 100% sure where, or if it’s even consistent between different graphics libraries/gpus, but let’s assume a horizontal offset of 512 pixels from the top left of the texture), I would instead copy it into the expected location for mip level 2 of a 1024x1024 texture. And then, since I’m already adjusting the UVs, sampling might “just work”?

I’m going to give #2 a shot and I’ll follow up with my results. But if anyone else has any other ideas/experiences, I would love to hear them!

Alright, following up in case this is useful for anyone else in the future, method 2 that I mentioned above does seem to work (and is automatically handled properly by Unity’s UNITY_SAMPLE_TEX2DARRAY).

1 Like

Another thread necro: here’s the solution that ended up working really well in my case. I need a single material that dynamically picks (on a per-triangle basis) which texture to draw from an array, but the multiple textures are all unrelated images of different sizes. The solution below performs really well on PCs with modern-ish GPUs (tried with GTX 1060) and on the Quest 2 (Adreno 650, high-end mobile), but not so well on the Quest 1 (Adreno 540).

Bind up to ~55 textures (or 14 on Quest 2) to different Texture2D variables. Then do this in HLSL:

SamplerState my_trilinear_repeat_sampler; /* the shader compiler uses words from this variable name, don't rename it too much! */
float4 SampleGlobMatTex(uint texture_id) {
[forcecase] switch (texture_id) {
case 0: return GlobMatTex0.Sample(my_trilinear_repeat_sampler, uv);
case 1: return GlobMatTex1.Sample(my_trilinear_repeat_sampler, uv);
case 2: return GlobMatTex2.Sample(my_trilinear_repeat_sampler, uv);
etc.
default: return float4(1, 1, 1, 1);
}
}

If you have more textures than that, too bad, you need multiple materials (or multiple MaterialPropertyBlocks with a single material). But still, this reduces the total number of materials by a factor 14 or 55, with considerable gain.

The number 14 on mobile is equal to the maximum 16 minus two unrelated textures needed by the same shader. The maximum is 64 on PC but I may have a few extra textures in use there, so I lowered the total to 55.

Are you aware of the reason why the limit is 64 on PC? All the resources I’ve come across state the texture limit is 128 on Direct3D 11. When I attempt to use over 64 textures, I get a warning in the console:
Shader '...' uses 71 texture parameters, more than the 64 supported by the current graphics device.

Ah, I didn’t even notice the documentation said 128. Maybe the theoretical maximum is 128 for the total number of textures used in the vertex shader plus the total number of textures used in the fragment shader? And Unity simplified that as “64 in each”? Just a random guess, though.

1 Like

Also of note, I have started testing on Android, looking to see if I would run into the same 16-texture limit you mentioned. I’m using a (relatively) budget, though recent, device (Samsung Galaxy Tab A9+) and it seems the limit is 32 as I am getting these warning messages:
Unity Shader '...' uses 34 texture parameters, more than the 32 supported by the current graphics device.

Looking at the hardware information on the linked page above, I’m not seeing anything that indicates a limit of 32 textures anywhere… It could be GL_MAX_TEXTURE_IMAGE_UNITS + GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS (16 + 16), but this would be the opposite of your previous suggestion that the 64 texture limit I’m seeing in the editor is logging only the limit for the fragment stage…

Edit:
I’m curious. On the Quest2, were you getting warning messages stating that the limit on that graphics device was 16? I just noticed my device also uses the Adreno ™ 650 that you mentioned is in the Quest 2. It would be interesting if the same GPU is reporting different texture limits.

I’m not claiming to understand it at all. Maybe we are not using the same graphics API? My numbers for the Quest are with OpenGL ES 3.x.

I’ve been using OpenGLES 3.2. I have not yet tested with Vulkan.

No idea. My experience with Quest as well as some internet resources seem to say that the 650 is really limited to 16…

1 Like