Certain render targets don't accept any draws while others do?

So this is a peculiar unexplainable issue. I’m writing a UI toolkit, well, a window manager in MonoGame using OpenGL. The way the window manager works is each window gets a RenderTarget2D that matches the size of the window and anything at all can be drawn to it.

The render target is only repainted if the window requests to be repainted - i.e new UI element added, a property on an existing element is changed that affects the appearance of the element, the user strikes a key or does something with the mouse that causes a UI update, etc, so that I don’t have constantly repainting windows and my game can run at 60FPS when idle.

If the window is resized, the render target is also resized, and the window is repainted. This works perfectly. Most of the time, windows draw on the screen perfectly fine - no graphical glitches, everything’s fine.

But there’s always this one time where this one window is completely stubborn and just doesn’t render at all. The render target exists, it matches the size of the window, it has its RenderTargetUsage set to PreserveContents, and it is in fact being rendered to the screen. I can prove this by drawing a red box underneath each window’s render target, which wouldn’t be visible because it’d be covered by a window. But, that’s not the case with these ghost windows.

This is my code for painting the windows. I know the bug lies in window painting and not rendering them to the screen.

public static void DrawControlsToTargetsInternal(GraphicsDevice graphics, SpriteBatch batch, int width, int height, ref List<Control> controls, ref Dictionary<int, RenderTarget2D> targets)
{
    foreach (var ctrl in controls.ToArray().Where(x=>x.Visible==true)) //only render controls that are set to be visible
    {
        RenderTarget2D _target; //the render target to render the control to
        int hc = ctrl.GetHashCode(); //the hash code of the control object, used for storage/retrieval of render target
        if (!targets.ContainsKey(hc)) //Do we already have a render target for the window?
        {
            //No, alright, create one.
            _target = new RenderTarget2D(
                            graphics,
                            Math.Max(1,ctrl.Width),
                            Math.Max(1,ctrl.Height),
                            false,
                            graphics.PresentationParameters.BackBufferFormat,
                            DepthFormat.Depth24, 1, RenderTargetUsage.PreserveContents);
            targets.Add(hc, _target);
            ctrl.Invalidate();
        }
        else
        {
            //Yes, alright, retrieve it.
            _target = targets[hc];
            if(_target.Width != ctrl.Width || _target.Height != ctrl.Height) //Does the control size match the target size?
            {
                //No, alright, recreate the render target.
                _target = new RenderTarget2D(
        graphics,
        Math.Max(1,ctrl.Width),
        Math.Max(1,ctrl.Height),
        false,
        graphics.PresentationParameters.BackBufferFormat,
        DepthFormat.Depth24, 1, RenderTargetUsage.PreserveContents);
                targets[hc] = _target;
                //ENSURE the target gets repainted
                ctrl.Invalidate();
            }
        }
        //Only repaint the target if the control needs updating.
        if (ctrl.RequiresPaint)
        {
            //Set the graphicsdevice target to the window target so we can draw to it
            graphics.SetRenderTarget(_target);
            //what does this do I have no f**king idea why is this here
            graphics.DepthStencilState = new DepthStencilState() { DepthBufferEnable = false };
            //begin draw call
            batch.Begin(SpriteSortMode.Immediate, BlendState.NonPremultiplied,
                            SamplerState.LinearClamp, GraphicsDevice.DepthStencilState,
                            RasterizerState.CullNone);
            graphics.Clear(Color.Black); //this should be Color.Transparent but I changed it to be black to see if it would turn these ghost windows into black boxes. It didn't work.
            var gfxContext = new GraphicsContext(graphics, batch, 0, 0, ctrl.Width, ctrl.Height); //GFX frontend that exposes methods for drawing lines, rectangles, text etc to the window
            gfxContext.Clear(Color.Black); //Use the gfx context clear method to create a black box - this doesn't work either
            ctrl.Paint(gfxContext, _target); //Actually paint the control - this doesn't do anything either
            QA.Assert(_target.IsContentLost, false, "A render target has lost its contents."); //Quality-assurance testing: if the target's content is lost there's something else wrong.
            QA.Assert(_target.RenderTargetUsage == RenderTargetUsage.PreserveContents, true, "A render target whose usage is not set to RenderTargetUsage.PreserveContents is being rendered to. This is not allowed."); //More QA testing, don't want lossy render targets in the window manager. That just causes these bugs.
                    

            batch.End(); //end draw call
            //try the QA testing again
            QA.Assert(_target.IsContentLost, false, "A render target has lost its contents.");
            QA.Assert(_target.RenderTargetUsage == RenderTargetUsage.PreserveContents, true, "A render target whose usage is not set to RenderTargetUsage.PreserveContents is being rendered to. This is not allowed.");
            //Set the game render target to the screen render target.
            graphics.SetRenderTarget(_game.GameRenderTarget);
            targets[hc] = _target;
        }
    }
}

It’s also worth noting the QA tests are passing. MonoGame’s literally refusing to render to these targets. I can’t provide screenshots or more code at the moment, but any help with this would be GREATLY appreciated.

Where are you disposing of the old render target when you resize?

Actually, I’m not disposing them anywhere. I should probably do that.

Anyway, I fixed the issue last night. The issue was caused by me adding UI elements to the toplevel list on a separate thread. Clearly MonoGame doesn’t like that at all.

So I made it so any requests to modify the toplevel/HUD list are executed on the game’s main thread and it fixed it right up with no performance loss.

The question now is… why did this even happen in the first place? Are RenderTarget2Ds not thread-safe? Is my UI engine not thread-safe? Well, at least the bug is fixed.

I’ll be making it dispose rendertargets when resizing though. That’s a pretty good idea I didn’t think of when writing the code lol

This thread prompted me to make this series:

http://www.vaseemvalentine.com/Content/Images/MGForum/ProgrammerThoughts/Programmer_Thoughts_001.jpg

So thank you :slight_smile:

Disposing of rapidly created objects is usually a good practice instead of relying on GC to come round, though, to be fair GC still has a mind of its own :stuck_out_tongue:

Generally you want to create any GraphicsResource on the same thread as the OpenGL context (for OpenGL). If you are not on the same thread then the actual creation of the “resource” on the GPU is held until the beginning of the next Update Loop. We do this by queuing up an action which will then be executed on the context thread.

It should block the secondary thread until the resource is created. So not sure why you had a problem here,

It should block the secondary thread until the resource is created. So not sure why you had a problem here,

To be honest, I’m not sure either. I know the actual creation of the window data was being done on a separate thread while the code I initially posted is executed on the game thread. The only thing I could guess is a race condition - the control being added to the top level list while the controls are being rendered, but then again that’d throw an InvalidOperationException in the foreach loop anyway.

Point is, I fixed it by invoking any code related to creating toplevels on the game thread - queuing them up so they run next Update().

I’ll definitely be lookin’ at that. My game needs a lot of optimizations.

1 Like