Yellow-Dog-Man / Resonite-Issues

Issue repository for Resonite.
https://resonite.com
143 stars 2 forks source link

UIX - Changing order offset of elements breaks masking #1463

Open LucasRo7 opened 9 months ago

LucasRo7 commented 9 months ago

Describe the bug?

If you have a UIX Mask under another image under a Horizontal Layout, if you change the order of the images under the horizontal layout, the Masked graphics start rendering behind the image

The problem is fixed if you create a copy of the UIX canvas(or save and reload the item) I couldnt find a way to "fix" this bug by disabling/enabling components, but putting the Slots in the original order does revert it back to how it looked before swapping

To Reproduce

Here are the steps to reproduce:

  1. Create a UIX Canvas

  2. Add a child with a Horizontal Layout component

  3. Create 2 childs, each with an Image component

  4. Under each child, add another child, with an image and a Mask component

  5. If you've done everything correctly you should have something like this: image

  6. Change the order offset of the slots under the Horizontal Layout so that they are in a different order, one of the Masks and anything under it will start rendering under the Image that is parented directly under the Horizontal Layout

Alternatively, here's a link to the folder with a sample item: resrec:///U-LucasRo7/R-9B973F98B21A2AF67CAF4F2B88E5E8A0C0B0EF351D2B0C4876B0EBA22311CC44 The sample item has a button that swaps the order of the slots under the Horizontal Layout, as described above

Expected behavior

Be able to change the order of elements under the Horizontal Layout and still having masked child elements rendering over the parents.

Screenshots

This image shows what happens to the sample Item in the folder resrec:///U-LucasRo7/R-9B973F98B21A2AF67CAF4F2B88E5E8A0C0B0EF351D2B0C4876B0EBA22311CC44

image (Red arrows indicate pressing the button, or changing the order offset manually)

Resonite Version Number

2024.3.1.1178

What Platforms does this occur on?

Windows

What headset if any do you use?

Desktop/Quest 2

Log Files

Not applicable, but here it is anyway: DESKTOP-CPP5K63 - 2024.3.1.1178 - 2024-03-09 18_53_22.log

Additional Context

This issue also happens with a Vertical layout. Changing the order offset in a way that doesnt change the order of the children does not cause the issue Toggling "Show Mask Visual" on the Mask component does not alter the results(sample item has it on for demonstration purposes)

Reporters

LucasRo7 (Discord: lucasro7)

JackTheFoxOtter commented 9 months ago

We had to work around the same bug when we made the dash calendar, where we were re-ordering the week rows displayed in the calendar for the sliding animation. It was a rather frustrating one that was pretty difficult to diagnose.

JackTheFoxOtter commented 4 months ago

Forgot this issue already existed, I'm adding the example from the ticket I've accidentally opened as duplicate to this one:

Describe the bug?

When having a nested mask on a UIX canvas, for example elements with masked parts inside of a masked scroll rect, updating the order offset of the individual elements leads to some of the elements being drawn back-to-front, where the background appears before the foreground.

To Reproduce

I've created a minimal example object to demonstrate the issue. Folder link: resrec:///U-JackTheFoxOtter/R-33BE1DF44ECA7C605039BA30E716C23775C588DEF003CF8D579E556C18314443

It contains a simple masked scroll rect with 5 elements, each having a background, a text (foreground) and a mask. Note that the background is partially transparent so that it's possible to see if something is rendering behind it. The number displayed on the text is the current order offset of that element. image

When clicking the "Flip" button to negate all order offsets, the last two elements in the newly sorted list get drawn back-to-front, where the background is rendered above the foreground. For non-transparent backgrounds, this makes it appear as if the content has disappeared completely. image

If you disable or remove the mask from the template, then re-generate the list, flipping the order offset works as expected, and the the draw order of the elements is not affected. image

Expected behavior

The individual elements should always be drawn in the correct order regardless of their order offset.

Resonite Version Number

Beta 2024.7.25.1284

Additional Context

I'm needing to dynamically re-order a list of complex elements (with nested masks) for a project I'm currently working on with xLinka and Tekno, this bug currently prevents us from re-ordering elements, which creates a big usability issue. This appears to be the same bug TheAutopilot and me ran into when working on the Dash Calendar for MMC23, so it has likely been around for a while.

You can see how without a transparent background, once the order is updated and the element runs into the reversed draw order issue, it appears as if the entire content has disappeared, when in fact it is just being rendered behind the background of the element. image

mpmxyz commented 4 months ago

I have found the root cause(s).

I have created an experimental fix.

Those are two Harmony patches:

       /// <summary>
       /// This patch shows how ordering the children of a GraphicsChunk by their natural order in the slot hierarchy fixes the masking issues.
       /// It is not designed to be efficient, but to show the principle.
       /// </summary>
       [HarmonyPatch(typeof(GraphicsChunk), "SortChildren")]
       class GraphicsChunk_SortChildren_Patch
       {//
           [HarmonyPrefix]
           static bool Prefix(GraphicsChunk __instance)
           {
               __instance.ChildrenChunks.Sort((GraphicsChunk a, GraphicsChunk b) =>
               {
                   var commonParentA = a.Root.Slot;
                   var commonParentB = b.Root.Slot;
                   Slot commonChildA = commonParentA;
                   Slot commonChildB = commonParentB;
                   //1. Find common parent and the element's location within it.
                   while (commonParentA.Parent != null && !b.Root.Slot.IsChildOf(commonParentA))
                   {
                       commonChildA = commonParentA;
                       commonParentA = commonParentA.Parent;
                   }
                   while (commonParentB.Parent != null && !a.Root.Slot.IsChildOf(commonParentB))
                   {
                       commonChildB = commonParentB;
                       commonParentB = commonParentB.Parent;
                   }
                   //2. Check the relation between the two elements.
                   //Note: This order may not be valid if there is no common parent.
                   //This should never be the case as "this" should be among the parents.
                   // It also doesn't handle two nested GraphicsChunks. This also shouldn't happen, right?
                   int num = commonChildA.OrderOffset.CompareTo(commonChildB.OrderOffset);
                   if (num != 0 && commonParentA == commonParentB)
                   {
                       return num;
                   }
                   return commonChildA.ReferenceID.CompareTo(commonChildB.ReferenceID);
               });
               //false: replaces whole method
               return false;
           }
       }

       private static MethodInfo _Canvas_MarkGraphicChunksDirty = typeof(Canvas).GetMethod("MarkGraphicChunksDirty", AccessTools.all);

       /// <summary>
       /// This patch ensures that SortChildren is called by the Canvas when the order of children changes.
       /// A proper marker should be implemented instead.
       /// </summary>
       [HarmonyPatch(typeof(RectTransform), "MarkChildrenOrderChanged")]
       class RectTransform_MarkChildrenOrderChanged_Patch
       {//
           [HarmonyPostfix]
           static void Postfix(RectTransform __instance, ref DataModelFlag ____dataModelFlags)
           {
               if (__instance.Canvas != null) {
                   _Canvas_MarkGraphicChunksDirty.Invoke(__instance.Canvas, null);
               }
           }
       }

Result

grafik

shiftyscales commented 4 months ago

Seeking input from @ProbablePrime or @Frooxius on the above findings.

ProbablePrime commented 4 months ago

Defer to @Frooxius still not entirely familiar with how UIX rendering works.