[Solved] Help with Letterboxing in Both Windowed and Fullscreen Mode

Just started trying out MonoGame and haven’t been successful in figuring out how to implement proper letterboxing. I’m not trying to create any game just yet because I want to figure this part out first.

What I’m trying to do is display the game area (Cornflower Blue) in 640x480 (the virtual resolution), while the game window itself is in 1280x720 (the actual resolution) with letterboxing on the sides (Black).

The reason I’m trying to do this is so I have a handy way of having resolution independence regardless of whether the game is in Windowed or Fullscreen mode. I know it should be possible to have letterboxing even in Windowed mode, but I just can’t figure it out.

Cant you render to a render target of the size/resolution you want, then render that texture to the screen how you want it, with the black borders you want?

So, you have a RT 640x480, you render your scene to that RT.

Then do (Psudo code)
Set RT
Clear
Draw Scene
unset RT

Clear.Black
spritbatach.Begin
spritebatch.draw(renderTarget,CenterScreen,color.white)
spritebatch.End

1 Like

Many thanks for the pseudo code! :slightly_smiling_face:
It really pointed me in the right direction, as I’ve almost gotten everything working now! :smile:

My only problem now is this line: spritebatch.draw(renderTarget,CenterScreen,color.white).
Can you tell me how to draw on “CenterScreen?”

I’m trying out a few things so the 640x480 window gets displayed in the center of the 1280x720 window.
Can’t figure out the simple math just yet though… :sweat_smile:

My current drawing code is:
spriteBatch.Draw(renderTarget, new Rectangle(WINDOW_WIDTH / 2 - VIRTUAL_WIDTH, 0, 640, 480), Color.White);

It’s the y position that’s primarily eluding me. Though now that I look at it, my x position isn’t really centered either. :sweat_smile:

1 Like

Screen center can be found with the following:-

Vector2 screenCenter = new Vector2(GraphicsDevice.PresentationParameters.BackBufferWidth / 2, GraphicsDevice.PresentationParameters.BackBufferHeight / 2);

If you use that raw value, you will need to set the origin in the spriteBatch.Draw method, otherwise offset it by half the RT size from it to place it in the center.

1 Like

Many thanks for the answer!
I also found that one of my constants had the wrong value, so even though I had the right formula, it wasn’t being positioned right. :sweat_smile:

Here’s my code. Hope it helps someone out there!

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace MGPlayground
{
    public class Game1 : Game
    {
        private const int VIRTUAL_WIDTH = 640;
        private const int VIRTUAL_HEIGHT = 480;
        private const int WINDOW_WIDTH = 1280;
        private const int WINDOW_HEIGHT = 720;

        private const int RENDER_X_OFFSET = WINDOW_WIDTH / 2 - VIRTUAL_WIDTH / 2;
        private const int RENDER_Y_OFFSET = WINDOW_HEIGHT / 2 - VIRTUAL_HEIGHT / 2;

        private GraphicsDeviceManager graphics;
        private SpriteBatch spriteBatch;
        RenderTarget2D renderTarget;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            IsMouseVisible = true;

            // TODO: Once 3.8.1 comes out, use this and remove the version in `Initialize`.            
            // graphics.PreferredBackBufferWidth = WINDOW_WIDTH;
            // graphics.PreferredBackBufferHeight = WINDOW_HEIGHT;
            // _graphics.SynchronizeWithVerticalRetrace = true; // Enable VSync.            
        }

        protected override void Initialize()
        {
            // TODO: Once 3.8.1 comes out, use the version in `Game1()` and remove this.      
            graphics.PreferredBackBufferWidth = WINDOW_WIDTH;
            graphics.PreferredBackBufferHeight = WINDOW_HEIGHT;
            graphics.SynchronizeWithVerticalRetrace = true; // Enable VSync.            
            graphics.ApplyChanges();

            renderTarget = new RenderTarget2D(
                GraphicsDevice,
                GraphicsDevice.PresentationParameters.BackBufferWidth,
                GraphicsDevice.PresentationParameters.BackBufferHeight,
                false,
                GraphicsDevice.PresentationParameters.BackBufferFormat,
                DepthFormat.Depth24);

            base.Initialize();
        }

        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);               
        }

        protected override void Update(GameTime gameTime)
        {
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
                Exit();

            base.Update(gameTime);
        }

        protected override void Draw(GameTime gameTime)
        {
            DrawSceneToTexture(renderTarget);

            GraphicsDevice.Clear(Color.Black);

            spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend,
                        SamplerState.LinearClamp, DepthStencilState.Default,
                        RasterizerState.CullNone);

            spriteBatch.Draw(renderTarget, new Rectangle(RENDER_X_OFFSET, RENDER_Y_OFFSET, VIRTUAL_WIDTH, VIRTUAL_HEIGHT), Color.White);

            spriteBatch.End();

            base.Draw(gameTime);
        }

        private void DrawSceneToTexture(RenderTarget2D renderTarget)
        {
            GraphicsDevice.SetRenderTarget(renderTarget);

            GraphicsDevice.DepthStencilState = new DepthStencilState() { DepthBufferEnable = true };

            GraphicsDevice.Clear(Color.CornflowerBlue);

            spriteBatch.Begin();

            // Put drawing code here.
            
            spriteBatch.End();

            GraphicsDevice.SetRenderTarget(null);
        }
    }
}
1 Like