Search Unity

  1. If you have experience with import & exporting custom (.unitypackage) packages, please help complete a survey (open until May 15, 2024).
    Dismiss Notice
  2. Unity 6 Preview is now available. To find out what's new, have a look at our Unity 6 Preview blog post.
    Dismiss Notice

Is there a way to force a texture to NOT compress?

Discussion in 'General Graphics' started by skwsk8, Jul 1, 2020.

  1. skwsk8

    skwsk8

    Joined:
    Jul 6, 2014
    Posts:
    36
    We've got a texture in the build and a texture created at runtime that are RGBA32 NPOT textures using a mix of SetPixel, SetPixels32, and Graphics.CopyTexture() in order to get some complicated lines drawn. If we do a buildTexture.height and a runtimeTexture.height they both show the same height, but when we use CopyTexture and pass in that height, it throws an error saying the height is half the size (the width is also half the size), so it seems like an auto compression. Is there a way to force the texture to *not* compress?

    The error we're seeing is on WindowsStandalone64:

    Graphics.CopyTexture called with region not fitting in destination element (dstX 1022, dstY 0, srcWidth 1, srcHeight 409, dstMip 0 -> dstWidth 1, dstHeight 409)
    //both texture sizes are 2045x818 and print out correct
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,365
    Textures will not get auto compressed at runtime unless you explicitly call
    Compress()
    on them from script, or are creating them with a compressed format, or have compression enabled on an asset.
     
  3. skwsk8

    skwsk8

    Joined:
    Jul 6, 2014
    Posts:
    36
    It's RGBA32 NPOT, which afaik is an uncompressed format. Here's the creation code (the one being written to):

    Code (CSharp):
    1. Texture2D waveformTexture = new Texture2D(WaveformRenderer.graphWidthInPixels, WaveformRenderer.graphHeightInPixels, TextureFormat.RGBA32, false) {
    2.             name = System.Guid.NewGuid().ToString().Substring(0, 8) + "_DynamicGraphTexture",
    3.             hideFlags = HideFlags.HideAndDontSave,
    4.             wrapMode = TextureWrapMode.Clamp,
    5.             filterMode = 0          
    6.         };
    7.         Color32[] pixels = new Color32[WaveformRenderer.graphWidthInPixels * WaveformRenderer.graphHeightInPixels];
    8.         for(int i = 0; i < pixels.Length; i++) {
    9.             pixels[i] = Color.black;
    10.         }
    11.         waveformTexture.SetPixels32(pixels);
    12.         waveformTexture.Apply(false, false);
    And the imported texture has the equivalent settings (the one being copied from):



    Unfortunately there's nothing calling Compress() on either texture, but the texture is used in the UI in a RawImage component and has an AspectRatioFitter on it's parent. Could that do it? Or is there some sort of global project setting that might opportunistically force compression? The SetPixel/SetPixels drawing to the texture works on the whole texture, but the Graphics.CopyTexture() only works on the bottom left quadrant.

    Have been playing wack-a-mole for weeks trying to get a dynamic line to draw in UI that adds a few points from the front of the line and/or removes a few points from the end of the line each frame (a la old school worm) with fast enough performance to run on WebGL, Windows Vive/RiftS VR, Android Quest VR, and Android Mobile.
     
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,365
    That doesn't show what format the imported texture is using. You need to look at the texture preview at the bottom of the inspector window to find out what format the texture is actually being stored as. Especially since while you're showing the standalone override tab, the override checkbox isn't checked, so that tab's settings are ignored.

    If there isn't a texture preview at the bottom of the inspector window, you probably hid that at some point. It's the UI element across the entire bottom of the inspector with the texture's name and two grey lines through it.
    upload_2020-7-1_10-20-57.png
    Click on that and the texture will appear showing some preview options, as well as the resolution, format, and memory usage.
    upload_2020-7-1_10-22-11.png

    You can see in the image example above, that's an uncompressed texture by it's RGB8 format. If it's compressed you'll see which format it's compressed in, like this:
    upload_2020-7-1_10-26-35.png

    Note, this is only the format for the currently active platform. If you need the texture to be uncompressed on all platforms, you'll either need to set the override for all platforms to the same format, or in your case you probably just want to go to the Default tab and set the format there.
     
  5. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,365
    If you want this to work on WebGL / Mobile, you honestly probably do not want to use anything that requires texture copy. You probably just want to have a material you update the UV offsets on every frame. Either from script or using a shader.
     
  6. skwsk8

    skwsk8

    Joined:
    Jul 6, 2014
    Posts:
    36
    Oh ya, I forgot to mention that I have it fall back to the slower SetPixels if CopyTexture isn't supported (WebGL / Mobile), which is fine since it's VR that's struggling perf wise. The windows standalone 64 does support it though so it's using the faster CopyTexture, it's just saying the size is 1022x409 in the error message, even though the texture height prints out as 2045x818, so it appears like the gpu mem copy is compressed anyway??



    BTW, there isn't a way to make a multi-point line shader that takes an array of Vector2, is there? Or any other faster approach for that matter?

    - I've tried UILineRenderer but that is limited by the VertexHelper, which is slower since it redraws every quad in the line even when points are pushed/popped.
    - I've tried CustomRenderTexture but since it needs to read/write in the same frame it needs to be double buffered, which is slower than SetPixel(s)/Apply.
    - I've tried making a custom shader in Shader Graph (not very familiar with HLSL) but it doesn't take vector2 array as input and making a 1000 vector2 variables in a visual scripting system could take days lol.
    - Currently "working" solution is the SetPixel/SetPixels32 with optional Graphics.CopyTexture, but it feels like there has to be a faster, more elegant solution... I hope xD
     
  7. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,365
    Compression doesn't modify the pixel dimensions. That'd be a mip map.

    I mean, it's possible. But you wouldn't want to. The way shaders work it'd have to calculate every single line at ever pixel to see if it's visible. That's not a good option.

    This is the solution, just don't modify the line mesh every frame. Use a texture with a dashed alpha on the line and move the UV over time.
     
  8. skwsk8

    skwsk8

    Joined:
    Jul 6, 2014
    Posts:
    36
    Ahh ok. So then the mip maps are being created/used somehow. I thought I had them all off to save perf since it changes every time:

    On the import settings "Generate Mip Maps" is off.
    In the dynamic Texture2D creation the final constructor param "mipChain" is false.
    In the updating section, any use of texture.Apply() has "updateMipmaps" false.
    In the Graphics.CopyTexture() calls, I don't think I'm triggering the use of mip maps. Here's the two uses:

    Code (CSharp):
    1. //CopyTexture(Texture src, Texture dst)
    2. Graphics.CopyTexture(waveformPoolManager.waveformTextureOriginal, graphTexture);
    Code (CSharp):
    1. //CopyTexture(Texture src, int srcElement, int srcMip, int srcX, int srcY, int srcWidth, int srcHeight, Texture dst, int dstElement, int dstMip, int dstX, int dstY)
    2. Graphics.CopyTexture(waveformPoolManager.waveformTextureOriginal, 0, 0, 0, 0, widthAmount, graphHeightInPixels, graphTexture, 0, 0, destinationX, 0);
    3. //this is the one that generates the warning
    ...am I?

    Good to know. I kind of figured that, but am grasping for straws.

    This would only work if we knew what the line was ahead of time, right? We've got fully dynamic lines where the points added are calculated every FixedUpdate().
     
    Last edited: Jul 2, 2020
  9. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,365
    So, I honestly have no idea what's creating mip maps, or if indeed mip maps are being created. Only that the resolution you mentioned would be a mip map.

    However the error message isn't saying that's the resolution of the image, that's saying that's the destination x offset, and src / dst area height. The error message is annoying not in the same order as the function's inputs. As for why you're getting the error, I have no idea.


    I still say the line based method is what you want to do. However you probably shouldn't be doing it on fixed update. Calculate the points and only set those points on the LineRenderer in LateUpdate. If you're actually using the UILineRenderer, that's not something written by Unity so I have no idea how efficient it is, but the same optimization should work: don't set the points on the renderer until you're done with the frame. I also don't know how frequently FixedUpdate is running for your project. I've worked on games where it was matched to the framerate, others where it was set to 2-3x the target framerate. The later could be monstrously expensive.
     
  10. skwsk8

    skwsk8

    Joined:
    Jul 6, 2014
    Posts:
    36
    Hmm ok, that's about where I'm stuck with that approach then. An idea was to try/catch and adjust if needed, but since it's only a warning, that's not possible. Alternatively, setting the approach to use CopyTexture or not based off of availability and platform in Awake() seems to do the trick (slower, but it "works").

    From what I understand, UILineRenderer is similar to LineRenderer except it works on UGUI. That was the first approach we took which worked fine in WebGL, but was too slow in VR since the VertexHelper re-drew the whole line whenever removing a point. And even with just the additive points, we only saw a 30% performance gain (3.8ms -> 2.7ms), but the SetPixel(s) was over a 70% gain oddly enough (3.8ms -> 1.1ms). I really do wish the UILineRenderer route was fast enough because I agree it feels like the proper implementation :/
    If LineRenderer does in fact work on UGUI in both screen space and world space, then we could double back and re-perf that as an option, but the previous developer on this task said that he couldn't get it working.

    We've been using FixedUpdate (interval of 0.02) since it's less frequent that Update (60-90fps so 0.0167-0.0111), but it should be easy to move to LateUpdate and set a custom interval with some basic code. Will give that a try.