Search Unity

Resolved Compressing mipmapped image at runtime sometimes causes artifacts

Discussion in 'General Graphics' started by Kevinsomnia, May 12, 2021.

  1. Kevinsomnia

    Kevinsomnia

    Joined:
    May 12, 2021
    Posts:
    2
    Hello,

    There is a strange issue that happens for specific textures when compressing them. This is the process that we follow in our main project:
    • Download image from web as uncompressed NPOT (non-power of two) texture using UnityWebRequestTexture.GetTexture() (but also reproducible in unitypackage by just importing as an asset).
    • Mipmaps are generated by copying over to another texture and calling Apply()
    • Compressed at runtime using texture.Compress(false)
    Rounding odd dimensions to an even number will usually fix it, but can sometimes fail.

    Doing one of the following will fix the issue 100%, but are not ideal solutions:
    - Don't run texture.Compress() (we'd like to reduce memory usage)
    or
    - Don't generate mipmaps (causes aliasing)

    See the image below:


    I checked the format of all of these images, and they are getting compressed at runtime, so I don't think it's because some of these images are NPOT (see how 256x832 and 512x832 display correctly and are compressed with mipmaps). Can someone explain what's going on here? I have attached a unitypackage so anyone can repro it if needed. Thanks!
     

    Attached Files:

    Last edited: May 12, 2021
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,336
    Runtime compressed textures on the PC will end up as DXT5 or DXT1 (depending on if they alpha or not). These are also known as BC1 and BC3.
    https://docs.microsoft.com/en-us/wi...programming-guide-resources-block-compression

    The DXT/BC texture formats are not limited to power of two resolutions, but are limited to multiples of 4. You're just lucking out that in many of your attempts the rounded value is a multiple of 4. 832 / 4 = 208. But that's why the 510x514 example is "failing", as neither 510 nor 514 are a multiple of 4. You can use values of 508, 512, or 516, but not 510 or 514. Really the fact any of this is working at all with non-power of two textures is kind of amazing though.

    You can fix everything by replacing all of your even checks (
    % 2
    ) and rounding (
    / 2) * 2
    ) to use the number 4 instead of 2. Or use
    Mathf.CeilToInt(target.width / 4f) * 4
    to get the next largest multiple of 4 resolution instead of always flooring the resolution.
     
    Kevinsomnia likes this.
  3. Kevinsomnia

    Kevinsomnia

    Joined:
    May 12, 2021
    Posts:
    2
    Ah, that makes sense. Thanks for your detailed explanation!
     
  4. jonaskhalil

    jonaskhalil

    Unity Technologies

    Joined:
    Apr 6, 2019
    Posts:
    33
    Hi, @bgolus is spot-on, you can still see issues if width and/or height aren't multiples of four in size (50% chance when rounding to even numbers :D).

    In fact, we've encountered this issue recently, as there are other issues with this as well (your texture data gets decompressed and uploaded, so you probably weren't really saving memory although I'm not sure the memory profiler properly reflects this). In the future, we're not going to allow users to compress textures if the dimensions aren't okay. If you look at the importer, you see that that restriction is already in there (just try and set "Compression" to "Low/Normal/High Quality" on any of those "issue ..." textures and you'll get a warning). We're planning to make this consistent all-the-way through so users will very clearly see that their textures don't have proper dimensions for compression (rather than silently playing along and then breaking in weird ways like you're seeing in your case).

    Also, I'm still a little curious about your fix to "not generate mipmaps" ... I should have a deeper look, but I'm wondering if that really works or if you're again just a couple of (clamped) pixels off and the diff is just not that obvious.

    "Really the fact any of this is working at all with non-power of two textures is kind of amazing though."
    Yes, D3D11 for instance only requires your highest-resolution "mip 0" to be nicely BC-block-aligned. See for instance the Direct3D 11.3 Functional Specification: "BC format surfaces are always multiples of full blocks, each block representing 4x4 pixels. For mipmaps, the top level map is required to be a multiple of 4 size in all dimensions." so no need for NPOT here.
     
    bgolus likes this.
  5. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,336
    On a side note, calling
    Compress()
    on the non-multiple of 4 resolution image results in a DXT1 compressed texture with a Mip 0 that looks correct (apart from compression artifacts), even if the lower mips are clearly off (mip map shoved into the corner of a larger pow2 resolution mip). That one really surprised me since most image compression tools just throw an error or complete garbage if you try to do that.