So I’m trying to create my own GUI for a project, mostly for the experience than anything else, and I’ve run into a perplexing series of… bugs, I suppose. Or features I’m misunderstanding! Either way, here’s what’s going on…
I need to create a clipped, scrollable area, such as what you’d find from a scroll pane, list view, scroll bar, etc. Basically a region in my UI that can have its content(which itself can be other controls like buttons and the like) scrolled up and down. Looking into how to do this, I found 3 main solutions recommended in older posts. Temporarily changing the viewport, using scissor rectangles, or stencil buffers. I chose to try scissor rectangle.
Then weird things started happening.
First of all its worth noting that I’ve rolled my own resolution independence solution, and so far as I can see it works just fine. It puts my game’s viewport in the correct place inside the window no matter how I shape the window. Whenever I do a spritebatch.Begin(), I pass in a transformation matrix which adjusts the scaling of drawn objects as well. That all seems to work. Here’s a snippet of the example draw code:
protected override void Draw(GameTime gameTime)
{
base.Draw(gameTime);
ShowTitleFPS(gameTime);
StateManager.Draw(SpriteDispatch);
SpriteBatch batch1 = new SpriteBatch(GraphicsDevice);
RasterizerState raster = new RasterizerState();
raster.MultiSampleAntiAlias = false;
raster.ScissorTestEnable = true;
GraphicsDevice.ScissorRectangle = new Rectangle(0, 0, 200, 200);
batch1.Begin(SpriteSortMode.Deferred, null, null, null, null, null, Resolution.RenderingScale);
Texture2D tex = new Texture2D(GraphicsDevice, 1, 1);
tex.SetData<Color>(new Color[] { Color.Red });
Rectangle coloredRect = new Rectangle(0, 0, 1280, 720);
batch1.Draw(tex, coloredRect, Color.White);
batch1.End();
}
You can see here that I’m NOT using the raster state or the scissor rectangle for this example. It’s just a normal batch with my transformation matrix from the Resolution Class. Since my virtual resolution is 1280x720, I get th expected result of the entire screen being filled in red.
Now, if I resize my monogame window, I correctly get letterbox or pillarbox effect with the drawn red square filling the viewport area inside:
Now let’s change the raster state to the one in the snippet. The only thing I’ve changed from the code snippet above is I changed the batch1.Begin() to include the ‘raster’ Rasterstate defind above. It turns on scissortest, and we create a clipping region with a 200x200 size at 0,0. Here’s the result:
So far so good. It’s at the top-left of the screen… but the keenly eyed will notice a snag. The box is indeed 200px, but it hasn’t been rescaled to the window size(which is 1600x900 btw). It’s just 200px… So theres oddity number one. The scissor test seems to ignored the transformation matrix. It operates purely on absolute size of the screen, or so it appears. This can be manually correct of course… but that’s not the biggest oddity. Let’s reshape the window. First pillarbox:
Huh? Where did the black backfill come from? And where’s my red square? We can’t see it because the scissor region is actually not inside the viewport. The scissorRectangle appears to position itself relative to the window, not to the viewport. Now letterbox:
HUH?! Not only is there this new black backfill, but the original cornflower backfill is randomly showing up below, not centered in any way… and suddenly the scissor rectangle is adjusting itself downwards, even though it didn’t do so for the X position earlier… and if the cornflower filled area represents my viewport, then why is the red square not fully inside it? And if the cornflower area doesn’t represent the viewport, then why is it part of it filled black and part cornflower?
I’m very, very confused right now. I can provide snippets of the Resolution class as well if anyone thinks it might help, though considering that everything works 100% as expected when I’m not using the scissorRectangle, I’m not sure it’s the way I’m coding the viewport.
And just to be sure, I tried removing the transformation matrix as well, which had no effect one way or the other. Also, I ran Console.WriteLine to print the dimensions of the GraphicsDevice.Viewport to output before and after batch1 begins and ends, and indeed both correctly showed {X:288 Y:0 Width:985 Height:554 MinDepth:0 MaxDepth:1}, when the window height was shortened to induce a pillarbox. X position looks fine, aspect ratio is correct… it’s all correct so far as I can tell.
What’s so bizarre about this is that programmatically the viewport is positioning itself correctly in the above example, yet the backfill incorrectly fills the bottom of the screen. Since the GraphicsDevice.Clear() is being called immediately after my Resolution class reshapes the viewport to the correct space, surely the backfill would be correct at least… but it isn’t.
As an aside, since the backfill is being added after the viewport size and position changes. I expected the cornflower blue to be contained inside the viewport, but it fills the entire window. This remains true in the non-scissortest example as well. Is that normal? I tried changing my viewport to the entire window, doing a black backfill, and then changing the viewport, and doing the cornflower backfill, expecting to see a viewport-sized cornflower inside black pillarboxing when I reshape the window… but it’s always cornflower… Except in the specific case of the scissorRectangle being turned on, when suddenly there’s black again.
It’s all seemingly so random that I can’t begin to fathom where to start debugging.
Am I missing something here?