Cooking your texture data - different APIs, resolutions and file sizes

Started by
8 comments, last by galop1n 6 years, 3 months ago

Things are pretty straightforward if you only target a single tier (which, let's be honest, is my case), but I've still been pondering how to go about basic scalability. 

Assumption: all textures are encoded with BC (DDS) 1/(3)/5/6/7 or in the future ASTC once that reaches mainstream. The target is the PC market.

Building BC-compressed textures is trivial for 1/3/5, but becomes a strictly offline process for versions 6 and 7. Moreover, while cooking textures for a single API (D3D or OpenGL/Vulkan in this case) is a fixed process, switching between the two requires swizzling blocks in the encoded texture. Again, this is fairly trivial for 1/3/5, but I'm not really aware of any publicly available implementation of how to do it for 6 and 7. In practice this means that the texture needs to be read and re-encoded for which ever API it wasn't cooked for. I'm assuming (sic!) this is also true for ASTC.

The same problem applies to resolution - scaling BC 1/3/5 textures down on the user's machine probably entails a fairly short preprocessing step during first run or installation, but re-encoding a couple of hundred or more BC 6/7 textures will probably end with the game getting uninstalled before it is even run.

So here are the options I can think of:

- target only one API and don't care about supporting both
- or target both APIs and ship all high quality textures for either platform. (or, you known, figure out how to swizzle BC 6/7 blocks). Reorder blocks for BC 1/3/5 for your non-preferred API.
- ship textures in 2-3 sizes (eg 4k, 2k, 1k), turning a blind eye to space requirements
- don't use texture compression for high quality textures

Any thoughts on a semi-automatic "best case" implementation?

 

Advertisement

You have mips anyway for all texture that are not pixel perfect UI elements, so scaling is just not loading the higher resolutions. Most APIs does not expose low level tiling/swizzling, so you are in fact API agnostic, and the memory reordering is usually cheap or perform on the GPU anyway in the driver.

 

I would forget about BC135 unless you are short on memory or runtime texture compression from dynamic data. You should stick just with BC467.

 

The trend in the industry is to perform additional encoding/compression over the BC for storage, leading usually to more processing at load time as a trade off (things more adoc than zip). For UI or not texture bandwidth intensive, it is also acceptable to store as JPG/PNG and just use uncompressed texture( like a splash screen ), but forget it for real textures as GPU does not read them as fast and it needs more bandwidth.

 

 

3 hours ago, galop1n said:

.... so you are in fact API agnostic, and the memory reordering is usually cheap or perform on the GPU anyway in the driver.

I meant the vertical UV coord difference between D3D and OpenGL. Unless I'm uninformed and D3D allows setting (0, 0) to the bottom left corner as in GL, you need to flip your textures vertically. For block-compressed data this means not flipping scanlines across the entire texture, but rather within each block. I'm unaware of a way to accomplish this for BC 6/7 post-compression - it might be possible, but it seems to be easier to just re-compress.  Which is too expensive.

This isn't an issue when targeting a single API, but unless I'm missing something, seems like a problem when trying to support both.

 

3 hours ago, galop1n said:

The trend in the industry is to perform additional encoding/compression over the BC for storage, leading usually to more processing at load time as a trade off

Hm - I'll give it a shot. The decompress can reasonably be performed once at first run so that doesn't seem like too much of an issue.

 

3 hours ago, galop1n said:

You have mips anyway for all texture that are not pixel perfect UI elements, so scaling is just not loading the higher resolutions.

This makes sense. So, ship at max resolution, but during loading simply feed data to the GPU from a different mip offset. I have to admit I was overthinking it :) 

For the texture loading thing, you don't really need to torture your textures. Just invert UVs in your shaders and or geometry

58 minutes ago, galop1n said:

For the texture loading thing, you don't really need to torture your textures. Just invert UVs in your shaders and or geometry

This may work in trivial cases, but not for geometry that uses texture tiling.

58 minutes ago, irreversible said:

This may work in trivial cases, but not for geometry that uses texture tiling.

I haven't done this myself, but couldn't you just multiply the V coordinate by -1 and then add 1? That should work even for tiled UV's. The V coordinate will often be negative, but that's fine since you're probably going to use a signed representation anyway for your UV's.

4 hours ago, irreversible said:

I meant the vertical UV coord difference between D3D and OpenGL. Unless I'm uninformed and D3D allows setting (0, 0) to the bottom left corner as in GL, you need to flip your textures vertically. For block-compressed data this means not flipping scanlines across the entire texture, but rather within each block

IIRC There is no vertical UV difference between APIs. There is a difference in the texture data loading APIs whether you're providing rows of pixels/blocks from the top down or the bottom up. The encoding of  blocks themselves is identical. You can use the same file format and UCs as long as you re-order the rows as required. Or you can use the same file format without reordering the rows (so that your textures are flipped) and then flip all your UVs. 

10 hours ago, Hodgman said:

There is a difference in the texture data loading APIs whether you're providing rows of pixels/blocks from the top down or the bottom up. The encoding of  blocks themselves is identical. 

This is actually the problem - yes, the encoding is identical, but all blocks are stored as 4x4 pixels, which are encoded using the original field order. In order to conform a regular DDS texture to GL, the order of individual scanlines (or UVs, as was pointed) need to be flipped vertically.

Which is to say, after a texture is encoded, the flip needs to be also performed within each block or you'll end up with a texture with each four-scanline horizontal slice flipped vertically. Simply flipping the rows of blocks would effectively flip the order of the blocks, but not individual scanlines. To fix that, pixels in a block can be swizzled during loading. For BC3 that looks something like this:


// (C) 2009 Cruden BV

static void FlipDXT3BlockFull(unsigned char* block)
{
	unsigned char tmp = block[0];
    block[0] = block[6];
    block[6] = tmp;
    tmp = block[1];
    block[1] = block[7];
    block[7] = tmp;
    tmp = block[2];
    block[2] = block[4];
    block[4] = tmp;
    tmp = block[3];
    block[3] = block[5];
    block[5] = tmp;
}

The problem is that while flipping encoded blocks is fairly easy for BC versions 1-5, the process is not as straightforward for BC 6/7 (and likely also ASTC), which AFAIK necessitates flipping the source texture and then reencoding it. Encoding a large BC7 texture can take on the order minutes so as far as I can tell the only realistic solution is to perform this during cooking.

This isn't something I'm just throwing out there, but rather something I'm currently dealing with in my own code.

As far as flipping the V coordinate goes, I'm still not sure how to that would work in all tiled cases (see below).

10 hours ago, MJP said:

I haven't done this myself, but couldn't you just multiply the V coordinate by -1 and then add 1? That should work even for tiled UV's. The V coordinate will often be negative, but that's fine since you're probably going to use a signed representation anyway for your UV's.

Suppose you have splatted surface or some sort of terrain and your V coordinate runs from 0.2 to 18.6 or some other similarly arbitrary tiling. The only way to flip that would be to know the UV bounds, which in itself can be cumbersome if not outright difficult in a shader.

Now, what if the texture coordinates are animated?

The formula v' = 1 - v works for every scenario if applied right before the tfetch. Tiling, animated, mirroring sampling. All that will work.

 

I can't certify it for sure, but it feels weird to me that you need actual texture data mirroring. I can't even remember a sampler or texture bit on GCN ( AMD GPUs ) that would control a V coordinate behavior difference on GL versus DX ( but i do not have the doc with me to double check ).

This topic is closed to new replies.

Advertisement