Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Programmer: What was the problem with IMGUI?

Discussion in 'General Discussion' started by Le_Tai, Oct 2, 2020.

  1. Le_Tai

    Le_Tai

    Joined:
    Jun 20, 2014
    Posts:
    442
    I started using Unity right around when uGUI first being released, so I spent too much time with it predecessor. As an Asset Store publisher, I do still work with IMGUI occasionally, and really loved its simplicity.

    With UI Toolkits on the horizon, I wonder why IMGUI is being completely phased out. I understand it have no GUI builder, and how it can be performance intensive for complex UIs. However, in other software engineering circle, I heard nothing but praise for imgui as a concept.

    Those who has worked with any kind of imgui in the past, Unity's or any other, what problems has the paradigm gave you personally?
     
  2. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,554
    None (used it in editor). Aside from performance.

    I actually thought the idea was really clever and really convenient, as you can make complex dynamic layouts this way, and it resembles functional programming somewhat.

    Kinda like Qt nailed OOP GUI design, IMGUI nailed code-driven design.
     
  3. MDADigital

    MDADigital

    Joined:
    Apr 18, 2020
    Posts:
    2,198
    Defining layout with code is not human readable. You cant look at the code and in realtime transform it into visuals like you can html/css or XAML/styles.

    MVVM and MVC fixes these problems. You abstract the code from the view in a clever way

    uGUI isnt perfect either, they should have gone with a MVVM approuch
     
    Last edited: Oct 2, 2020
  4. Le_Tai

    Le_Tai

    Joined:
    Jun 20, 2014
    Posts:
    442
    Right? A such a shame it being phased out
     
  5. Le_Tai

    Le_Tai

    Joined:
    Jun 20, 2014
    Posts:
    442
    You never actually worked with these, did you?
     
    jdtec, akuno, neginfinity and 2 others like this.
  6. MDADigital

    MDADigital

    Joined:
    Apr 18, 2020
    Posts:
    2,198
    I have worked with both. Well, mostly my colleague who is the UI designer but some for me too. Our entire UI is made in uGUI. The old crap we only use for our internal editor tools
     
  7. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,617
    Most of the issues I had with it were in the implementation, not the concept. Stuff like some info being hard to access in certain contexts, or some accessible info being highly context dependent in unclear and sometimes inconsistent ways.

    Still, you mention performance and... yeah... that's a big one, even in the Editor. I've got a few objects in my current project which make the game noticably slower just from showing in the Inspector while testing.

    Huh? HTML defines the content, not visuals. That's what CSS is for, unless you're working with really old stuff or ignoring best practices.
     
    MadeFromPolygons likes this.
  8. MDADigital

    MDADigital

    Joined:
    Apr 18, 2020
    Posts:
    2,198
    See edit. css is not perfect but alot better than anything else. styles in XAML is crap, but alot better then the out of the box uGUI stuff.
     
  9. Le_Tai

    Le_Tai

    Joined:
    Jun 20, 2014
    Posts:
    442
    I worked with all kind of XML based retained mode UI. They all get extremely complex very fast. In fact, nowaday retained mode UI are being written in a more immediate mode like syntax, see Flutter or Swift UI.
     
  10. Le_Tai

    Le_Tai

    Joined:
    Jun 20, 2014
    Posts:
    442
    For editor code, my experience is that a big part of the performance hit is the reflection heavy architecture, not so much imgui itself.
     
    angrypenguin likes this.
  11. MDADigital

    MDADigital

    Joined:
    Apr 18, 2020
    Posts:
    2,198
    A what you see is what you get editor can be nice, maybe, I don't use it for web or app work. But I know some do, none of the pro web deisgner or web app UI guys I worked with though.
     
  12. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,617
    That's a big aspect, for sure.

    Another is that the whole GUI is re-run every time something might have changed. I change something in a text field and the whole GUI it's a part of gets re-run, including all of the serialisation and/or reflection stuff going on to populate it. And the result of that might be one extra character appearing in a text box.

    I have one UI that basically shows a table of data from a SerializedObject. It's pretty simple stuff. Alas, when imeplemented the way the docs say, after there are more than a few dozen rows it gets to the point where I can type faster than the input can be handled.
     
  13. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,617
    I've only said bad things, so it sounds like I'm really sour on it. :p

    For the most part it's a nice and quick way that I can throw simple custom inetrfaces together, and in that regard it works pretty well. It's only when things get complicated that it runs into issues. And some of those issues I could probably overcome by not using the *GUILayout classes, because they (reasonably enough) come with a bunch of assumptions.
     
  14. MDADigital

    MDADigital

    Joined:
    Apr 18, 2020
    Posts:
    2,198
    Biggest problem with IMGUI is that something as simple as

    Code (CSharp):
    1. <ul bind="foreach: items">
    2.    <li bind="text: Name"></li>
    3. </ul>
    Becomes alot of code, and as more elements you bring in, the harder the code gets to read and transform to actual layout flow in your brain
     
  15. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,294
    Immediate mode GUI is a great way for making relatively useable UI very quickly, with very little code. Unity's implementation does just that, but it's
    - very hard to make it look good
    - very poorly performing
    - very, very buggy
    - has a poorly designed API full of inconsistencies and holes.

    It's very good for adding a few buttons to an editor UI in a situation where it's fine that it looks just like any other Unity editor control. An alternative is needed when you either need to make something that looks good, are doing something complicated, or have very much data to edit.

    So getting rid of it for in-game uses isn't a big deal, since if we want to make any UI that looks decent, it's a no-go. It allows Unity to strip out some code from the engine, which speeds things up for us (and for their iteration speed).

    It's going to stay there in the editor, which is good - while UI Toolkit gives better results, it takes longer to get things done, and you often don't need that good results when what you're doing is adding a readout of some data or a button that does a debug thing.

    UGUI can go die in a fire, it should never have been shipped, it's never been production ready, God it's so bad.

    To MaKe A tExTbOx FiT iTs CoNtEnT, FiRsT aDd A HoRiZoNtAlLaYoUtGrOuP.

    ????

    Code (csharp):
    1. foreach(var item in items) {
    2.     EditorGUILayout.LabelField(item.name);
    3. }
     
  16. MDADigital

    MDADigital

    Joined:
    Apr 18, 2020
    Posts:
    2,198

    Now you want the text to float right ;)

    You get the point, defining views in code get messy and unreadable fast, very fast.
     
  17. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,554
    If a situation like that causes trouble for a developer, you need to replace that developer with another one that has more skill.

    As this kind of thing is very trivial to do.

    Nope. 2 lines of code for two controls is pretty much definition of readability.

    "Making things float right" is not the problem solved by UI layout generator code, but by stylesheets.

    --------

    Maybe it would be a good idea to implement alternative UI toolkit that uses the same approach.

    As I said, it is one of the two cleanest approaches I ever saw when it comes to generating UI from code.
     
  18. MDADigital

    MDADigital

    Joined:
    Apr 18, 2020
    Posts:
    2,198
    In a somewhat comlex UI it will be a huge pain.
     
  19. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,294
    I don't think you can be performant at scale - at least by default - with any imgui approach.

    Say you're drawing some thousands of elements in a list with a scroll view. In an immediate approach, you don't know anything upfront about how many elements there are, or how large they are, so you have to (by default) run the code for every element every time you want to update anything on the screen.

    So what you end up having to do is for the developer to manually roll their own thing to handle that performance issue, or some other workaround.
    In a retain mode you by definition have the elements and their sizes upfront, so culling the list ends up being based on data you already have.

    But for the other things, yeah. GUIStyle could've been good, the API could have been well designed, and the bugs could have not been there.
     
  20. MDADigital

    MDADigital

    Joined:
    Apr 18, 2020
    Posts:
    2,198
    Example, still pretty trivial UI but its a pain to read, this is a real world example of a editro tool we have made for our game. Code is not perfect, just quick made for use in editor


    Code (CSharp):
    1. GUILayout.BeginHorizontal("box");
    2.  
    3.             if (session.Instance)
    4.                 if (GUILayout.Button("Revert"))
    5.                 {
    6.                     PrefabUtility.RevertPrefabInstance(session.Instance.gameObject);
    7.                 }
    8.  
    9.             if (session.Instance)
    10.                 if (GUILayout.Button("Apply"))
    11.                 {
    12.                     ApplyPrefab(session.Instance);
    13.                 }
    14.  
    15.             if (GUILayout.Button("Refresh session"))
    16.             {
    17.                 RefreshSession();
    18.                 Next(0);
    19.             }
    20.  
    21.             if (GUILayout.Button("Toggle hands"))
    22.             {
    23.                 HandIKRigger.ToggleHandConfig();
    24.             }
    25.  
    26.             GUILayout.EndHorizontal();
    27.  
    28.             GUILayout.BeginHorizontal("box");
    29.  
    30.             if (GUILayout.Button("Prev. rigg"))
    31.             {
    32.                 Next(-1);
    33.             }
    34.  
    35.             if (GUILayout.Button("Next rigg"))
    36.             {
    37.                 Next(1);
    38.             }
    39.  
    40.             if (Selection.gameObjects.Length != 0)
    41.                 if (GUILayout.Button("Highlighted as secondary"))
    42.                 {
    43.                     SetupSecondary();
    44.                 }
    45.  
    46.             GUILayout.EndHorizontal();
    47.  
    48.             GUILayout.BeginHorizontal("box");
    49.  
    50.             if (session.Rigg)
    51.                 if (GUILayout.Button("Highlight rigg"))
    52.                 {
    53.                     Selection.objects = new[] {session.Rigg.gameObject};
    54.                 }
    55.  
    56.             if (session.Item && Selection.gameObjects.Length != 0 && GetEndTrigger())
    57.                 if (GUILayout.Button("Copy trigger to end point"))
    58.                 {
    59.                     CopyTriggerStart();
    60.                 }
    61.  
    62.             GUILayout.EndHorizontal();
    63.  
    64.             if (HasRigError())
    65.             {
    66.  
    67.                 GUILayout.BeginHorizontal("box");
    68.  
    69.                 if (GUILayout.Button("Correct finger tip rotation errors"))
    70.                 {
    71.                     var errors = new List<Transform>();
    72.                     GetErrorBones(session.Rigg, errors);
    73.                     foreach (var error in errors)
    74.                     {
    75.                         error.localEulerAngles = new Vector3(0, 0, error.localEulerAngles.z);
    76.                     }
    77.                 }
    78.  
    79.                 if (GUILayout.Button("Highlight finger tip rotation errors"))
    80.                 {
    81.                     var errors = new List<Transform>();
    82.                     GetErrorBones(session.Rigg, errors);
    83.                     Selection.objects = errors.Select(t => t.gameObject).ToArray();
    84.                 }
    85.  
    86.                 GUILayout.EndHorizontal();
    87.  
    88.             }
    89.  
    90.  
    91.             var prefab = EditorGUILayout.ObjectField("Jump to", null, typeof(Transform), true) as Transform;
    92.             if (prefab)
    93.             {
    94.                 Jump(prefab);
    95.             }
    96.  
    97.             var template = EditorGUILayout.ObjectField ("Rig from", null, typeof(Transform), true) as Transform;
    98.             if(template)
    99.             {
    100.                 CopyRig (template);
    101.             }
    102.  
    103.             if (Selection.gameObjects.Length != 0)
    104.             {
    105.                 EditorGUILayout.LabelField("Selected transform");
    106.  
    107.                 var trans = Selection.gameObjects[0].transform;
    108.  
    109.                 var pos = EditorGUILayout.Vector3Field("Position", trans.transform.localPosition);
    110.                 if (trans.localPosition != pos)
    111.                 {
    112.                     trans.localPosition = pos;
    113.                 }
    114.  
    115.                 var rot = EditorGUILayout.Vector3Field("Rotation", trans.transform.localEulerAngles);
    116.                 if (trans.localEulerAngles != rot)
    117.                 {
    118.                     trans.localEulerAngles = rot;
    119.                 }
    120.  
    121.                 var scale = EditorGUILayout.Vector3Field("Scale", trans.transform.localScale);
    122.                 if (trans.localScale != scale)
    123.                 {
    124.                     trans.localScale = scale;
    125.                 }
    126.  
    127.             }
    Looks like this
    upload_2020-10-2_14-4-32.png

    Could look like this in some pseudo UI framework

    Code (xml):
    1. <panel>
    2.    <button name="RevertPrefab">Revert</button>
    3.    <button name="ApplyPrefab">Apply</button>
    4.    <button name="RefreshSession">Refresh session</button>
    5.    <button name="ToggleHands">Toggle hands</button>
    6. </panel>
    7. <panel>
    8.    <button name="PreviousRig">Prev. rigg</button>
    9.    <button name="NextRig">Next rigg</button>
    10.    <button name="HighlightAsSecondary">Highlight as secondary</button>
    11. </panel>
    12. <panel>
    13.    <button name="HighlightRig">Highlight rigg</button>
    14. </panel>
    15.  
    16. etc. etc
    , by convention if a button is bound to method HighlightAsSecondary a property bool CanHighlightAsSecondary can be used to determin if button can be pressed, etc. Works very well for Caliburn Micro in WPF
     
  21. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,294
    Come to think of it, here's another problem in imgui:

    Code (csharp):
    1. for (int i = 0; i < list.Count; i++) {
    2.     EditorGUILayout.LabelField(list[i]);
    3.     if (GUILayout.Button("delete")) {
    4.         list.RemoveAt(i);
    5.         i--;
    6.     }
    7. }
    And now your UI explodes.

    The problem here is that Unity does the UI in several passes. Since the UI in later parts of the code affects how UI in earlier parts look (ie. more elements in a horizontal layout makes the earlier ones smaller), Unity has to do a layout pass first that doesn't draw anything, but just gathers information about what's there.

    This means that during that pass, Unity doesn't know where the button is, and can't check if it's clicked.

    Later on, it does a pass over the same code, knowing where all the elements are, and it can handle buttons. And as it handles buttons, the number of elements change. Now all the info Unity's implementation had about where all the UI elements were is outdated, and it explodes.


    Since the code later on has to be run in order to know where the button is, this is kind of an unavoidable problem. The only tractable immediate mode workaround I can think of is to change Button so it takes a delegate instead of returning a bool. If you do that, you can run the layout code and gather all of the button's delegates. Then you can deal with the buttons clicked after all the layout's done.

    But that takes away from the simplicity of writing the code, and I don't think it solves more complex cases, so it might just be a fundamental part of any imgui ui.
     
    MadeFromPolygons likes this.
  22. MDADigital

    MDADigital

    Joined:
    Apr 18, 2020
    Posts:
    2,198
    Thats the fundamental problem with the whole thing, you mix view rendering with data
     
  23. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    20,965
    I'm not able to answer the question directly because my time with Unity's UI systems started after Unity 5's new UI system was released, but I can direct you towards Stack Exchange where this question received many answers.

    https://gamedev.stackexchange.com/questions/24103/immediate-gui-yae-or-nay
     
    MadeFromPolygons and MDADigital like this.
  24. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,554
    No, this can be dealt with by a programmer, by removing assumption that "every element can change its shape every frame to anything without reason and logic and number of elements can also change every frame".

    Basically you would have a single function that takes multiple lambdas, including one that can query whether any elements in a range have changed.

    Not to mention that situation with THOUSANDS of dynamically sized elements in a scroll view is something you never see in a modern game. Spreadsheet-like games tend to use pagination and fixed row/column sizes.

    So, this is perfectly manageable.

    Code (csharp):
    1.  
    2. var indexesToKill = new List<int>();
    3. for (int i = 0; i < list.Count; i++) {
    4.     EditorGUILayout.LabelField(list[i]);
    5.     if (GUILayout.Button("delete")) {
    6.         indexesToKill.Add(i);
    7.     }
    8. }
    9.  
    10. //elsewhere
    11. for (int i = indexesToKill.Count - 1; i >= 0; i--){
    12.     list.RemoveAt(indexesToKill[i]);
    13. }
    14.  
     
  25. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,554
    This dude gets it:
    https://gamedev.stackexchange.com/a/24552

     
  26. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,294
    Yes. I wrote that later in the post you quoted. The issue is still that you have to write a lot more stuff in that case than in the retained mode case, where "running the code too many times" just isn't a problem that exists.

    Yeah, I know, I have written that code a thousand times.

    The appeal of IMGUI is that it's easy to create stuff. That's the whole point. With both of those cases, it's apparent that working with a dynamically sized list in imgui is a bit toss.
     
  27. MDADigital

    MDADigital

    Joined:
    Apr 18, 2020
    Posts:
    2,198
    Its easy until its not. Glad we started on unity 5 so we didnt have todo actual UI code in it.

    Its passable for editor tools (as long as its simple editor tools)
     
  28. neginfinity

    neginfinity

    Joined:
    Jan 27, 2013
    Posts:
    13,554
    Well, if it is a pattern, then the code like that should be moved into a library, for example, you could write a generic version of that removeAt trick one and keep using them again and again.... same goes for dynamic list views and so on. You write it once, and then use a generic version you wrote once everywhere.

    The thing is, I do not believe that an object based approach to GUI is inherently more sound. It always felt bloated and clumsy and the only decent OOP based approach I saw was Qt one. They nailed it with signal/slot architecture.

    Here it felt like this is an amazing idea that would need more polish.

    It is highly likely, however, that unity's implementation of said idea was not that great.