Since I've got this a bit more hammered out now, here's how the structure of new-style tilesets will be.
Each tileset is a directory, let's say foo.
When DF opens the tileset, it reads a descriptor file - foo/tileset.txt. It might look something like this:
A test tileset
base.png 16 16
extras.png 8 4
dwarves.png 4 10
The first line of the descriptor is the title, for use in the in-game tileset switching dialog. (Yes, you'll be able to do that while it's running, including a hotkey to reload it..)
Each line below that links to one tileset file; these are loaded in order. The numbers after the filename denote the width and height of the tileset in tiles. The filename itself does not affect DF.
So, with the above example, you'd get 328 tiles. Number 0 through 255 would be base, 256 and up extras and dwarves. These are loaded into a texture array, in order; in a shader, you just refer to them with that number. In non-shader modes, 0 through 255 are used - these don't need to be all in the same file, by the way.
I've yet to decide what to do about graphics mode, but it will probably involve a buttload of programmatically written #defines to let you refer to the textures in shaders; in other words, textures from raw/graphics will get loaded at texture array indexes higher than those of the base tileset. You also get a #define GRAPHICS, so you can #ifdef out code if you want.. well, it's pretty flexible, but you don't have to use all this.
You may also have custom per-tileset shaders; foo/shader.fs and foo/shader.vs will override the default (example) ones in data/art.
(If the tileset referred to is an image file instead of a directory, it just loads a 16x16 tileset - same as today.)
Any questions? Objections?