mob-sakai / SoftMaskForUGUI

Enhance Unity UI (uGUI) with advanced soft-masking features to create more visually appealing effects!
https://github.com/mob-sakai/SoftMaskForUGUI
MIT License
1.97k stars 261 forks source link

Performance impact of GetPixelValue #73

Closed TigerHix closed 4 years ago

TigerHix commented 4 years ago

We had serious performance issues on medium-low end devices with SoftMasked elements on canvas with graphic raycaster. The following is a screenshot of the profiler when the mouse is on top of the SoftMasked element (or, equivalently, press down on the screen if using a phone).

OQT4N%T~T J0EU`PQ3RKX1K

From my understanding, the bottleneck apparently came from SoftMask.IsRaycastLocationValid (which is called by EventSystem.Update on all masks), where the method calls SoftMask.GetPixelValue to get the alpha value of the hit pixel from the texture using GetPixelValue, and it returns true if alpha is greater than 0.5f. The problem is GetPixelValue is very expensive. It seems like it is reading pixels and setting the render texture every time:

        /// <summary>
        /// Gets the pixel value.
        /// </summary>
        float GetPixelValue(int x, int y, int[] interactions)
        {
            if (!s_ReadTexture)
            {
                s_ReadTexture = new Texture2D(1, 1, TextureFormat.ARGB32, false);
            }
            var currentRT = RenderTexture.active;

            RenderTexture.active = softMaskBuffer;
            s_ReadTexture.ReadPixels(new Rect(x, y, 1, 1), 0, 0);
            s_ReadTexture.Apply(false, false);
            RenderTexture.active = currentRT;

            var colors = s_ReadTexture.GetRawTextureData();

            for (int i = 0; i < 4; i++)
            {
                switch (interactions[(i + 3)%4])
                {
                    case 0: colors[i] = 255; break;
                    case 2: colors[i] = (byte)(255 - colors[i]); break;
                }
            }

            switch (_stencilDepth)
            {
                case 0: return (colors[1] / 255f);
                case 1: return (colors[1] / 255f) * (colors[2] / 255f);
                case 2: return (colors[1] / 255f) * (colors[2] / 255f) * (colors[3] / 255f);
                case 3: return (colors[1] / 255f) * (colors[2] / 255f) * (colors[3] / 255f) * (colors[0] / 255f);
                default: return 0;
            }
        }

The easiest workaround for this, which worked for us, is to let IsRaycastLocationValid always return true, bypassing the GetPixelValue check. Since in our use case, we don't really care about the which parts of the masks are "raycastable" - we just need the soft mask to render correctly (for example, showing a circle-shaped UI element). Maybe you can have something like RaycastVisiblePartsOnly as a SoftMask parameter? Or is there a better workaround?

I am not really good at Unity's graphics stuff, so please correct me if I incorrectly understood something. Thanks!

mob-sakai commented 4 years ago

@TigerHix As you say, raycast validation should be optional.

github-actions[bot] commented 4 years ago

:tada: This issue has been resolved in version 0.10.0-preview.1 :tada:

The release is available on:

Your semantic-release bot :package::rocket: