A question about culling optimizations.

Note: I am NOT referring to CullMode when I say “culling”. I’m referring to the simple principal of not drawing a 2D object if it is off-screen or covered by another 2D object.

Alright, so I have some code I am working on, from my game’s engine.

foreach (var ctrl in _topLevels)
{
    if (!ctrl.Visible)
        continue;
    if(ctrl.Opacity>0)
       ctrl.Draw(time, ctx);
}

ctx.Device.SetRenderTarget(_plexgate.GameRenderTarget);
ctx.BeginDraw();
foreach (var ctrl in _topLevels)
{
    if (!ctrl.Visible)
        continue;
    if (ctrl.BackBuffer != null && ctrl.Opacity > 0)
    {
        if (IgnoreControlOpacity)
        {
            ctx.Batch.Draw(ctrl.BackBuffer, new Rectangle(ctrl.X, ctrl.Y, ctrl.Width, ctrl.Height), Color.White * _uiFadeAmount);
        }
        else
        {
            ctx.Batch.Draw(ctrl.BackBuffer, new Rectangle(ctrl.X, ctrl.Y, ctrl.Width, ctrl.Height), (Color.White * ctrl.Opacity) * _uiFadeAmount);
        }
    }
    else
    {
        ctrl.Invalidate();
    }
}
ctx.EndDraw();

Note: ctx is of type GraphicsContext, a class I wrote that wraps various functions in SpriteBatch and GraphicsDevice. Documentation is not yet available, but the code should seem pretty trivial to most MonoGame users.

Now let’s assume a few things about this code.

  1. _topLevels is class member of type List<Control>.
  2. Control is an abstract class that represents a UI element, with a parent UI element and child UI elements.
  3. Control.Draw(GameTime, GraphicsContext) is a virtual method that handles painting child controls to the control’s surface as well as painting the control’s UI to its own surface.
  4. Controls can overlap eachother, can respond to mouse/keyboard events, and can be dragged around.
  5. Thing #4 applies to top level controls (what this code above is rendering).
  6. This code is run every frame.

There is a slight problem. This problem affects the rendering of child controls as well as handling of mouse events, but I want to deal with the simplest part of the problem, rendering of toplevels.

The problem isn’t something visual. It’s simply the fact that this code’s approach to rendering does not handle overlapping controls very well. Let’s say you have two objects, a circle and a square. Both are in the same exact spot as far as coordinates go, and both are the same size. The square is sitting ontop of the circle in the Z-coordinate (you should only see the circle if the square is translucent or invisible).

How does one, in that case, prevent the circle from rendering completely? Of course, as far as rendering goes you would switch that off if the square was translucent, but let’s say they’re both solid. How can I adjust this code so that it doesn’t render UI elements that are completely covered by other UI elements?

The reason I ask this is because we currently have a framerate issue that although it wouldn’t bug most people, it’s really irking me. I want to apply a similar culling optimization to the mouse handler so that only controls that aren’t covered by other ones receive mouse events, that’ll help out a lot, but let’s solve the visual stuff first.

Any help is greatly appreciated :smiley:

Can you treat the controls as rectangles? Calculating if a rectangle is fully contained inside another rectangle is quite simple. If you want other shapes, it can get complicated.

Is your UI very dynamic? For (mostly) static UI’s, you could render your UI to a rendertarget once, and then render that rendertarget every frame, instead of all the individual controls. Since you only render your UI once, occlusion optimizations might not be worth it.

All controls are treated as rectangles, yes. And I don’t plan on touching the code that deals with control surfaces (each control has two rendertargets, one for the backbuffer and one for the surface, and there’s a bunch of logic behind the scenes for rendering that stuff and it’s already incredibly optimized, don’t wanna break it).

Even if this doesn’t have a huge performance benefit, it could still help for mouse events and making sure that controls which the player can’t see can’t be clicked on or hovered over.

The MonoGame rectangle class has a Contains(Rectangle) function.
How about doing this check for all potential occluder-controls?

As for mouse events, there could be an alternative approach, that may or may not work for you. Assuming the controls have some kind of HandleMouseEvent function, you could sort your controls by depth. Then call the HandleMouseEvent function in depth-sorted order, front to back. The first control that successfully handles the mouse event, also swallows the event, so no controls behind it can recieve it.

That’s already how I do it for top-levels. Tried doing that for children but it broke…somehow. Gonna look at it some more. The main issue I have is with mouse move events, they’re handled differently for reasons I don’t want to explain too much (basically better drag-and-drop support), and said events are the ones with the issue.