Extremely odd Render Target issue.

So I have code like this in my game, part of its UI system.

public void Draw(GameTime time, GraphicsContext gfx, RenderTarget2D currentTarget)
{
    if (currentTarget == null)
    {
        Invalidate();
        return;
    }
    if (_isVisible == false)
        return;
    //gfx.Device.SetRenderTarget(_rendertarget);
    //gfx.Device.Clear(Color.Transparent);
    //gfx.Device.SetRenderTarget(currentTarget);
    if (_invalidated)
    {
        if (_resized)
        {
            if (_rendertarget != null)
                _rendertarget.Dispose();
            _rendertarget = new RenderTarget2D(gfx.Device, _width, _height, false, gfx.Device.PresentationParameters.BackBufferFormat, gfx.Device.PresentationParameters.DepthStencilFormat, 1, RenderTargetUsage.PreserveContents);
            if (_userfacingtarget != null)
                _userfacingtarget.Dispose();
            _userfacingtarget = new RenderTarget2D(gfx.Device, _width, _height, false, gfx.Device.PresentationParameters.BackBufferFormat, gfx.Device.PresentationParameters.DepthStencilFormat, 1, RenderTargetUsage.PreserveContents);
            _resized = false;
        }
        gfx.Device.SetRenderTarget(_userfacingtarget);
        gfx.Device.Clear(Color.Transparent);
        gfx.BeginDraw();
        OnPaint(time, gfx, _userfacingtarget);
        gfx.EndDraw();
        _invalidated = false;
    }
    gfx.Device.SetRenderTarget(_rendertarget);
    gfx.Device.Clear(Color.Transparent);
    gfx.BeginDraw();
    gfx.DrawRectangle(0, 0, _userfacingtarget.Width, _userfacingtarget.Height, _userfacingtarget, Color.White, System.Windows.Forms.ImageLayout.Stretch);
    gfx.EndDraw();

    gfx.Device.SetRenderTarget(currentTarget);
    gfx.Device.Clear(Color.Transparent);
    gfx.BeginDraw();
    gfx.DrawRectangle(0, 0, _rendertarget.Width, _rendertarget.Height, _rendertarget, Color.White, System.Windows.Forms.ImageLayout.Stretch);
    gfx.EndDraw();
    foreach (var ctrl in _children)
    {
        if (ctrl.RenderTarget != null)
        {
            if (ctrl.Control._resized)
            {
                ctrl.RenderTarget.Dispose();
                ctrl.RenderTarget = null;
                ctrl.Control.Invalidate();
            }
        }
        if (ctrl.RenderTarget == null)
        {
            ctrl.RenderTarget = new RenderTarget2D(gfx.Device, ctrl.Control.Width, ctrl.Control.Height, false, gfx.Device.PresentationParameters.BackBufferFormat, DepthFormat.Depth24, 1, RenderTargetUsage.PreserveContents);
            ctrl.Control.Invalidate();
        }
        gfx.Device.SetRenderTarget(ctrl.RenderTarget);
        ctrl.Control.Draw(time, gfx, ctrl.RenderTarget);
    }

    gfx.Device.SetRenderTarget(currentTarget);

    foreach (var ctrl in _children)
    {
        gfx.BeginDraw();
        if (Manager.IgnoreControlOpacity)
        {
            gfx.DrawRectangle(ctrl.Control.X, ctrl.Control.Y, ctrl.Control.Width, ctrl.Control.Height, ctrl.RenderTarget, Color.White, System.Windows.Forms.ImageLayout.Stretch);
        }
        else
        {
            gfx.DrawRectangle(ctrl.Control.X, ctrl.Control.Y, ctrl.Control.Width, ctrl.Control.Height, ctrl.RenderTarget, Color.White * ctrl.Control.Opacity, System.Windows.Forms.ImageLayout.Stretch);
        }
        gfx.EndDraw();
    }
}

Basically, my user interface system is similar in structure to Windows Forms. Each UI element has its own set of children, a parent element (null if it’s a top-level control), a UI manager instance which allows the control to access things like the currently focused element, the screen size, config settings for the UI, etc.

I’ve heavily optimized my UI engine. Each control has a set of render targets, all three of which match the width and height of the control. One, the front buffer, is where user painting is rendered to. It is only modified if the control is invalidated. Thus, we only need to run user painting code “every now and then” instead of every frame. The back buffer render target is mofified every frame and is where the front buffer is rendered to.

Then there’s the parent render target, it’s where the back buffer is rendered to, except the parent control (or the UI manager itself) manages it rather than the control being rendered.

That’s where our problem arises. Those two foreach statements that operate on the _children list are where the issue really is. Specifically, the first foreach block.

So I have a number of checks in there to make sure the parent buffer is okay to render to - things like “is it null? does it match the width and height of the control?” etc. If a check fails, the render target is regenerated so that all the checks will pass. Once the render target is ready, I call the “Draw” method on the control, telling it to render to the parent buffer. Easy-peasy. And, it works. Because when I run my game and open my WIP settings menu, I get a UI that looks like this.

Simple! You get a text block that tells you its opacity, and two buttons - one with an image adornment, one without. Except, that’s not what the UI is supposed to look like. Layout-wise, it’s fine. Everything’s in the right spot…but it’s…missing something.

Specifically, it’s missing a fourth UI element - a picture box - that has its texture set to my game’s logo. And it is CERTAINLY registered as a child of that window. You can see this if you draw a red box under each child element:

Notice how the red box outlines the transparent/translucent areas of each child element? And notice how there’s a big solid red box right underneath the “This text is 50% transparent” text?

That’s where my picture box is! It’s there! The UI engine KNOWS it’s there! So what the heck’s going on here!? Well, it seems that every child past the first three simply do not render to their parent buffers. They render their back and front buffers just fine - but their parent buffers end up fully transparent. And you can see this by simply changing the order in which each control is added to their parent.

Note: My window border’s caption buttons are just hitboxes with the window border rendering the textures on its own. They’re not affected by the bug. That’s normal.

I moved the button with the image so it is the fourth child of the window, and as you can see the picture box now renders just fine, and the button with the image is now a red box. The heck?

Let’s try adding a fifth UI element. I’ll add a text control just below the picture box.

Alright…so…guess the issue’s changed. The text below the picture renders just fine, and it renders AFTER that button that ISN’T rendering.

So what’s the pattern!? I KNOW now that it’s not EVERY control after the third that’s affected by the bug… could it only be the fourth? Could it be every fourth? Who knows? I don’t.

But I do know that, forgive my language, this bug is pissing me off. And I want to get it fixed. And I’m out of ideas. I had a similar bug where only ONE child would render, and I fixed that by moving the parent buffer checks to the Draw() method. That fixed it, but caused this bug.

Edit 1: It’s only the fourth.

Only the fourth render target is affected by the bug. What’s going on here!?

By default render targets are wiped when a new target is set (https://gamedev.stackexchange.com/questions/90396/monogame-setrendertarget-is-wiping-the-backbuffer)

You can control this behavior but if I’m not mistaken not all platforms support all behaviors so you might be losing some platforms if you change it.

Could it be your problem? Without your code it’s hard to tell but sound like it might be it.

PS. You might be doing some pre-mature optimizations there. Think about every game you ever played - how many UI entities are actually visible on the screen at any given time? 10? 20? 50?? Using render targets to optimize those quantities won’t make any difference and would be a waste of memory. Try to calculate how many draw calls you’ll actually need and think if you really must have the optimization.

RenderTargets however can be super useful to handle overflow and scrolling, which is a different story.

Just my two cents :slight_smile:

1 Like

My code’s right at the top of the post… :L

Anyway, no, that’s not the source of the issue. If it were, NONE of the render targets except for the last ever used one would have their contents - and judging by the 4 or so pictures I posted, that’s not the case.

As for the optimization, I really do need it. My game is entirely UI-based. The Peacenet’s “world” is really just a Unix-like operating system simulated by the game. Without limiting the amount of programs the user can run in said OS, there really is no way to detect if the user has 50 thousand UI elements at the same time going. Also, I do intend to use them for scrolling and overflow so…yeah.

I’ve basically narrowed it down to a bad GraphicsDevice property on the affected render target but…I’m not even sure because I can’t find any subtle differences between a graphicsdevice property on a working target and the one on the bad one. I’m just guessing because that’s what caused the bug that I fixed, whose fix caused this one.

Oops sorry I’m on mobile and missed it, that was kinda silly. I’d still try to play with the render target mode only because its a test that doesn’t cost anything and can eliminate an option.

Does changing the order of elements affect the bug? If you put the image first or only the image and non of the others does it change anything?

Again, all that information can be found in the initial post. Including screenshots that show the affects of doing such tests.

Status update: I added a click event to an arbitrary element in my ui, and in the event, I removed the element affected by the bug, then subsequently added it back. It popped back in and worked…wat!?

do not render to their parent buffers. They render their back and front buffers just fine - but their parent buffers end up fully transparent. And you can see this by simply changing the order in which each control is added to their parent.

Is it possible the parent drew before the child drew to it ?
er vice versa.

Do You have hi def profile on, could this be a render target limit ?

I do have the HiDef profile on. Could that be it?

Also, can’t be a rendertarget limit because if I remove and re/add the affected UI element sometime after the initial render, it works fine.

And the way this framework is designed, the parent can’t draw itself overtop children…not the way this code’s written.

if I don’t remove and re/add the affected UI element sometime after the initial render, it doesn’t work fine.

if I remove and re/add the affected UI element sometime after the initial render, it works fine.

Sounds like you have it narrowed.

Turn on your console in your project property settings thru the application → output type ( drop down ).
Stick some console writeline code in there that fires on a keypress.
Print out the before and after values. See whats different.

So I redid the code so there’s now only TWO render targets per control, a back and a front buffer. Control paints to the front buffer, front buffer paints to back buffer, back buffer paints to parent. This kinda pushed the bug to another spot. Now, instead of the bug affecting the FOURTH child, it’s affecting the ninth child. Everything else is the same. Here’s the new code.

public void Draw(GameTime time, GraphicsContext gfx)
{
    bool makeBack = _invalidated; //normally I'd let this be false but I thought I'd try making the backbuffer reset if the control's invalidated. This seemed to help, but right after restarting the game and doing the same thing, the bug was back. So this only works intermitently.
    if (_rendertarget == null)
        makeBack = true;
    else
    {
        if (_rendertarget.Width != Width || _rendertarget.Height != Height)
        {
            _rendertarget.Dispose();
            _rendertarget = null;
            makeBack = true;
        }
    }
    if (makeBack)
    {
        _rendertarget = new RenderTarget2D(gfx.Device, Width, Height, false, gfx.Device.PresentationParameters.BackBufferFormat, DepthFormat.Depth24, 1, RenderTargetUsage.PreserveContents);
    }

    if (_invalidated)
    {
        if (_resized)
        {
            _userfacingtarget?.Dispose();
            _userfacingtarget = new RenderTarget2D(gfx.Device, Width, Height, false, gfx.Device.PresentationParameters.BackBufferFormat, DepthFormat.Depth24, 1, RenderTargetUsage.PreserveContents);
            _resized = false;
        }
        gfx.Device.SetRenderTarget(_userfacingtarget);
        gfx.Device.Clear(Color.Transparent);
        gfx.BeginDraw();
        OnPaint(time, gfx);
        gfx.EndDraw();
        _invalidated = false;
    }

    gfx.Device.SetRenderTarget(_rendertarget);
    gfx.Device.Clear(Color.Transparent);
    gfx.BeginDraw();
    gfx.DrawRectangle(0, 0, Width, Height, _userfacingtarget);
    gfx.EndDraw();
    foreach(var child in _children)
    {
        child.Draw(time, gfx);
        gfx.Device.SetRenderTarget(_rendertarget);
        gfx.BeginDraw();
        if (Manager.IgnoreControlOpacity)
        {
            gfx.DrawRectangle(child.X, child.Y, child.Width, child.Height, child.BackBuffer);
        }
        else
        {
            gfx.DrawRectangle(child.X, child.Y, child.Width, child.Height, child.BackBuffer, Color.White * child.Opacity);
        }
        gfx.EndDraw();
    }
}

I’m using render targets for one of my UI controls as well and I noticed some very odd behaviour. Specifically, try as I might, I wasn’t able to properly clear a render target in spite of trying several different methods (creating a new one, setting empty data, etc…).

Something you may want to try is to reproduce this same test using pure XNA and see if there might be a bug somewhere in MonoGame. If you see the same behaviour in XNA at least you know it’s “intended” (ish).

Another thing to test, if you haven’t already, is use a single render target and don’t keep it around. Instead, manually copy the contents to another texture (via SetData). It’s certainly not an optimal approach, but it again it might help provide some info to you.

I don’t have a pure XNA development environment and with how big the game is I am certainly not going to go through all the projects and move 'em over. So I can’t really do that…

Anyway I’ve come up with a workaround. If I reset the back buffer when the control is invalidated, if I get a bad control backbuffer I can just hover over the hitnox and it pops back in. Not the most awesome solution ever but at least I can actually design my UI and test to see if everything at least works as intended.

Sorry, what I meant was, can you reproduce this issue in a smaller scale environment. Haha I’m certainly not suggesting you port your project over to pure XNA… though realistically, it should actually just work :slight_smile:

I know when I run into these issues, if I can recreate it in as small a code base as possible, it tends to be a little easier to identify the problem. It certainly helped me when diagnosing this issue…

I didn’t find a solution, but I managed to create a very small project that reproduced the issue. Given that the same problem did not occur without MonoGame involved, it led me to determine that I was working within the MonoGame problem space exclusively.

Sometimes when I do this, I actually can’t reproduce the issue at all and that leads me to believe the problem is somewhere within my own code. Just strategies to help diagnose and debug.

Anyway, it’s interesting that if you hover over it fixes it. Is the code that runs when you hover doing anything interesting?

Yeah, I forced it to do that as part of the genuine features of the UI. If the mouse enters or leaves a UI element it should invalidate said element to allow it to update.

So if I just reset the backbuffer when invalidated, it fixes it.