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.