Search Unity

Assets [WIP] RefreshUI – Lightweight & Fast UI

Discussion in 'Works In Progress' started by capkoh, Apr 15, 2019.

  1. capkoh

    capkoh

    Joined:
    Oct 1, 2016
    Posts:
    12
    Hi,

    I decided to share RefreshUI with you. The system that I'm working on right now.

    Before going forward I would like to let you know why this topic even exists. If you've been following Unity for some time you might know that it has been missing a solid UI system for a long time. There were some external systems. And then Unity released New Unity UI. To be honest, I decided that it's the end. I thought that we finally got UI system that seamlessly integrated into Unity and perfectly works with all the features that Unity provides.

    The more I worked with New Unity UI the more inconveniences I found. I don't want to go deep into it because everything there is subjective. But at some point I decided that it's enough. That moment RefreshUI has started. I will also try to present issues with concrete examples in this topic.

    Consider checking out RefreshUI if you are not feeling completely happy with New Unity UI.


    What is RefreshUI 1.0?

    It is a set of core components that allows you to create any UI element or control. It is not a library of UI components that you could drag and drop into your project. You need to spend some time to create a set of components that suits your needs best. Why so? In every project there are always specific requirements for UI elements that are defined, for example, by localization system or tutorial system used. However, RefreshUI comes with a Demo (super basic screen with a button for now) that has some components implemeted and you could use those to learn and create your custom ones.


    What is the difference to New Unity UI?

    No Canvases
    RefreshUI does not have canvases or anything that might look like it. It does not require anything to be on top of hierarchy. The only requirement is to have UIManager script somewhere in the scene. And you only need it to handle input.

    No Manual Batching
    RefreshUI does not try to batch things for you. It relies on Unity's Dynamic Batching feature. It means that every element in RefreshUI has MeshRenderer and MeshFilter components. These components are created at runtime. It also allows you to optimize batching easier as you could use Frame Debugger to see exactly why certain element is not batched.

    Does not depend on Hierarchy
    RefreshUI does not depend on hierarchy order to render things. Every UI element is rendered as a true 3D object within scene among other objects. To control render order of transparent objects with orthographic camera you have to use transform's Z position. Farther objects will be rendered first. For 3D UIs that use perspective camera you could additionally specify sorting layer and order the same way that SpriteRenderer does to avoid transparency sorting order issue.

    No RectTransform
    RefreshUI does not use RectTransform to position elements. It means that there is no layout engine in RefreshUI. If you want to position elements relative to each other you could use UIElementModify component only for those elements that really need it. Want to set element's position? Use Transform component as for every other object in Unity. Want to set element's size? Get UIElement and set Size property there.

    No Update, No Start, No OnEnable, No LateUpdate, No Coroutines
    UI elements in RefreshUI do not use per-frame update functions in any way. It means that there is no overhead on calling these functions. It is especially important if you have a very complex UI with a huge amount of elements there. Note that some of these functions are used in the Editor to provide editor functionality, but they are completely stripped from the build as they all are under UNITY_EDITOR define.

    Refresh when you need (changed in version 1.0.6)
    RefreshUI does not apply changes to elements immediately. Instead, refresh is scheduled and will be executed at the end of the frame. It is perfectly valid to force refresh for any element by calling Refresh on it if your code depends on the result of refresh. This approach decreases amount of branching in property setters to zero and makes code easy to read, understand and extend.

    Proper Multiple Pointers Support
    RefreshUI always works in multiple pointers mode. It is up to you to decide if to work with single pointer or multiple pointers. It means that you could have a world map that you could pan and zoom with two or more fingers and have buttons on top that work with single pointer. If you want to handle multiple pointers in your custom control, then use UIControl as a base. If you are making a button or want to use only one pointer, then use UISinglePointerControl as a base. Also UIManager allows you to prevent multiple clicks (taps) in the same frame. This is the issue that almost every game with multiple pointers support is suffering from. Just call CancelPointers after your click event. See DemoButton implementation for details.

    Particle Systems
    With RefreshUI you could use built-in Particle Systems without any tricks or hacks as UI elements are true 3D objects in the same world with particles. By the way, you might see 2D/3D particle systems for RefreshUI that scale properly soon.

    Warning – Animations
    Please note that due to the nature of RefreshUI it does not support animations created in Animation Window because they change serialized properties directly, and there is no way to understand what exact properties were changed. There is a workaround for this, but I do not want to implement it right now since it will force me to add a copy for every serialized property to hold "old" value. Not sure that it worth it. Remember that you could always use code-driven animations.


    Additional Features

    Accurate SDF Fonts Optimized for Mobiles
    RefreshUI has optimized SDF fonts implementation for mobiles. It uses very simple shader, but still supports plenty of features including accurate gradient that goes correctly through all the letters. Also it has styles support and, obviously, allows you to use multiple styles in a single label. The most important thing is that it allows you to import fonts in the same size easily. It means that you could change fonts in the entire game without adjusting size or position for a single label. Very useful for localization and simplifies working with labels a lot.

    2018.3
    RefreshUI is designed for Unity 2018.3 and above. It is fully compatible with new nested prefabs workflow.

    RefreshUI License
    Please read RefreshUI-License.txt file provided with package.


    Notes

    RefreshUI is a WIP and will be updated from time to time. It might change a lot in the future, but the core concepts mentioned above will stay the same.

    As there is no documentation or tutorials at the moment, I consider this post as a milestone that I will use to go forward. I'm going to start to work on documentation soon.

    Note that package contains executable file called fmaker_x64.exe and FreeType library freetype.dll. These files belong to a tool that generates SDF font atlases and it only works on Windows. Also it contains Roboto-Regular.ttf from Google Fonts.

    You could download package and use DemoScene as a starting point.
    Feel free to dive into the code and investigate! Enjoy!

    :)

    Konstantin Sarychev


     

    Attached Files:

    Last edited: Sep 11, 2019 at 5:28 PM
    Xerioz and Circool like this.
  2. capkoh

    capkoh

    Joined:
    Oct 1, 2016
    Posts:
    12
    Meanwhile I'm thinking about the format of the documentation I want to share an interesting test. In fact I'm confused with the result and I will explain why later.

    Test 1 – 4000 Moving Sprites

    The test is to use a huge number of moving Images (UGUI) and UISprites (RefreshUI) to check what has higher FPS on my Xiaomi Mi A2. You could download project that I used in the attachments and build it for your device if you want to test it too. I was using Unity 2018.3.0 with Mono backend. It has two scenes: Canvas and RefreshUI. Both scenes have the same setup and use the same scripts to duplicate and move objects around.

    I setup scenes to have 4000 moving objects in both cases. You could adjust the amount in the script on Duplicator object, but make sure that you changed it in both scenes. In case of UGUI I have extra Canvas in the scene. In case of RefreshUI I have nothing, it does not require extra objects to work. UIManager is created on the fly as a singleton.

    Here are the results (also see attached screenshots from device):
    • Manual Batching (UGUI) that is done by Canvas: 9 FPS
    • Dynamic Batching (RefreshUI) that is built-in in Unity: 46 FPS
    Screenshot_20190417-215301.png Screenshot_20190417-215250.png

    Now you see why I am confused by the result? Basic Dynamic Batching is 5 times faster than Canvas. Of course it is an extreme test, but why the difference is so huge? And why Unity decided that it's fine? For me it is a disaster.

    Well, explanation is easy. Canvas is doing layout & mesh building that ruins performance. The most annoying thing here is that I don't even need (or use) layout features in this test. I'm just animating some objects in the canvas. If you have a look at the attached image with the profiler from device, you will see that UI stage takes crazy amount of time in each frame! Interesting that without UI stage both approaches look the same. On the image: left part – Canvas with UGUI, right side – Dynamic Batching with RefreshUI. Note that it shows Editor target because I saved profiler data and loaded it later to take a screenshot.

    canvas_vs_dynamic_batching.png

    Unfortunately, I don't know if there is a way to disable layout operations in UGUI. Do you know if there is any? I really don't like when I have features that I can not disable when I'm not using them. Maybe one day Unity provide this possibility. But until then I will stick with RefreshUI.
     

    Attached Files:

    Last edited: Apr 20, 2019
  3. capkoh

    capkoh

    Joined:
    Oct 1, 2016
    Posts:
    12
    Hi,

    I have updated package in the first post to version 1.0.2.
    Package has been tested in Unity 2018.3.0.

    Changes:
    1. Added support for OpenType fonts kerning using GPOS table.
    2. Fixed crash and improved packing algorithm for both atlases and fonts.
    3. Fixed bug when controls became unresponsive immediately after launch.
    4. Fixed some edge cases and greatly improved Undo & Redo support in Editor.
    Very first video about RefreshUI is coming in the next few weeks. I had to spend some time writing meaningful script, preparing video & audio capture and improving my pronunciation. It is not going to be a tutorial video because I think that it is important to talk about few core things first.

    Once again I would like to emphasize that RefreshUI is a living project and there are still things that I'm not completely happy about. For example I have some concerns about UIModifyElement script and still thinking how to improve it even if it is doing what I want it to do.

    See you soon!
     
  4. capkoh

    capkoh

    Joined:
    Oct 1, 2016
    Posts:
    12
    Hi,

    Finally, the first video is here!

    In this video I talk about Transform, Size and Pivot and how to work with them. I think this is a perfect topic for the first video. Please let me know what you think about it. Is it clear what I am talking about? Do you want more examples or deeper explanations?

    And I have one really important question. I think it's possible to create tutorial video while making a tutorial game at the same time so that in the end of the tutorial there will be a small game. Would you prefer this type of tutorial?


    Let me know what you think!

    :)
     
  5. capkoh

    capkoh

    Joined:
    Oct 1, 2016
    Posts:
    12
    Hi,

    There is small update on what I'm working on right now. But first please allow me to do small introduction. If you check version 1.0.2 then you will see that for the font preset there you could define face properties and outline properties. Both could be assumed as a separate layers. So, in version 1.0.2 you could have only 2 layers.

    Now it is possible to add as many layers as you want and make even more interesting styles! Check out screenshots below. But the best thing is that it does not break old functionality and does not increase number of draw calls!

    layers.png

    On the first screenshot you see 4 draw calls because all labels are separate objects plus draw call for background sprite. But you could use text tags and put all 3 styles into the same label and all 3 styles will be rendered in 1 draw call. It is possible because all properties for the style are passed as vertex attributes and all 3 styles share the same font face. Check out image in the middle. There are only 2 draw calls (remember, 1 for background).

    Every style that you see on the image contains 4 layers. Each layer is represented by extra quads for the characters that belong to that layer, so, yes there is overdraw. But you could make normal text with 1 layer (and get no overdraw) and make titles and headers with nice N-layered styles!

    You could check what properties are available for each layer on the right side of the image. It shows 2 top layers for Gold Coast style. I think it's just enough for now. Of course you could modify code and use your custom material to add more functionality.

    Styles are working now, but I am not entirely happy how they work. Looks too complicated to me. So, I want to simplify it before publishing version 1.0.3. Once I finish with it I will also do a video about UISprites!
     
    Last edited: Jun 29, 2019
  6. capkoh

    capkoh

    Joined:
    Oct 1, 2016
    Posts:
    12
    Hello,

    Version 1.0.3 is now available in the first post. This is a minor update that fixes some small issues with undo and new prefabs workflow. And... it adds one epic feature! This feature is layers for UILabel styles! It is described in the post above.

    Unfortunately, I have not had much time to do video about sprites in RefreshUI, but anyway I decided to put new version here to keep package in a good shape. I really hope to have more time and concentrate on videos, there is a lot of interesting stuff happening under the hood.

    If you are using older version, then please restart Unity after you import new one. There is a bug in Unity that could cause some references to ScriptableObjects to become nulls. I think that it is fixed in the most recent Unity version.

    See you!
     
  7. capkoh

    capkoh

    Joined:
    Oct 1, 2016
    Posts:
    12
    Hi all,

    New video is now available on YouTube. This one is about UISprite and UIAtlas. All the basics to start to use them properly. One point might be unclear from the video. The points is that it is not required to put every object in a separate prefab, you could still create one single prefab for a screen or popup. It could contain any number of atlasable sprites inside and it will work fine with atlas generator.

    Please also note that RefreshUI 1.0.4 is now available in the first post. It contains some quality of life improvements that I did while I was working on the video.

     
  8. capkoh

    capkoh

    Joined:
    Oct 1, 2016
    Posts:
    12
    Hi,

    This is a small update that addresses performance issue in StaticCache<T> class. The problem was that it was using new constraint and hence all new T() calls inside it were converted into Activator.CreateInstance<T> calls by the compiler. Activator.CreateInstance<T> is known to be very slow. So, it was a huge issue since cache is supposed to create and manage a lot of objects fast. The solution was to use delegates to create instances of the known types. It is much faster than Activator.CreateInstance<T> and I am happy with the result.

    There is one design flaw in my solution, so check StaticCache.cs file if you're interested to know it. ;)

    Version 1.0.5 is now available in the first post!
     
  9. capkoh

    capkoh

    Joined:
    Oct 1, 2016
    Posts:
    12
    Hi all,

    Version 1.0.6 is now available in the first post! In this version I reviewed Refresh functionality in a way that it is no longer necessary to call Refresh after changes to the properties if your code does not rely on the result of Refresh call. Now refresh is scheduled automatically at the end of the frame and executed by UIManager. It is still valid to call Refresh directly to apply all the changes immediately if it is required.

    The reason I did it is that when I added UIColorMixer I realized that potentially any script could change any property for any element and it would force this script to call Refresh. Otherwise changes will not be applied. But imagine that there are 3 scripts that, for example, modify color of the element. It makes sense to apply only the final color value to the mesh. Without refresh schedule mesh will be modified 3 times and it is clearly a waste of resources, because only the final color matters. With refresh schedule mesh will be modified once, but at the end of the frame.

    As I mentioned before, it is still allowed to call Refresh anytime and it will apply changes immediately. For example, if text of the label in Auto mode is updated and its size is used in the next line of code, then it is required to call Refresh. Otherwise, it will only be updated at the end of the frame.

    In conclusion, starting with version 1.0.6 only call Refresh if your code does rely on the result of Refresh call.

    See you!