XNA 2D Independent Resolution Rendering

Note: 22/04/2013 – Due to popular request I made an article about achieving this effect in OpenGL

Independent Resolution Rendering?? What’s this all about?

Basically a way of not caring what you resolution is. Ever had Gui elements misplaced because you changed the resolution? Or getting out of the screen?

If you are doing a game on Xna just for Xbox360 you can basically use a 1280×720 base resolution and the Xbox will scale the game for you making the proper Letterbox.

But what about on Windows? Or if you use a different resolution on the Xbox? You have to manage that yourself.

I’ve made a small example on how to achieve this.

By the means of a class that I called Resolution ( just change it for whatever you feel it’s better) you can set both Virtual and Actual Resolution. Virtual or Base resolution is what I call the actual resolution in which I’ll work everything, and you stick with it, for both moving sprites, calculations etc. It’s your working Resolution. The other one is the resolution at which the game is rendering, which we want to be independent of the game. So the class will scale your Virtual to the Actual, making a LetterBox or a PillarBox to match them.

Resolution.SetVirtualResolution(1280, 720);
Resolution.SetResolution(800, 600, false);

This is telling that you are working as if the game is on a 1280×720 but it is rendering on a window of 800×600.
The third flag is fullscreen or windowed.

On the main Draw Pump just add the following so that the class can make the appropriate viewport.

Resolution.BeginDraw();

Next whenever you make a SpriteBatch.Begin() you have to pass the Resolution Scale Matrix as the forth parameter:

spriteBatch.Begin(SpriteBlendMode.AlphaBlend,
                                   SpriteSortMode.Immediate,
                                   SaveStateMode.SaveState,
                                   Resolution.getTransformationMatrix());

Now you can draw everything on the same way you would normally do

spriteBatch.Draw(_texture, Vector2.Zero, Color.White);

This said you can now change to different Resolutions keeping the same base and the same code and this will scale everything for you, neat right?

Here are a few screenshots of an application (using Machinarium) with a Virtual resolution of 1024×768 on different real resolutions

An another example (using Braid) with a Virtual Resolution of 1280×720

You can download the project with the source here. Once again if you run into any mistakes (most probably 😛 ) let me know.

More Reading

Post navigation

  • Nice one 😉

    My engine is working with a fixed resolution only, so this will help me a lot 🙂

  • This is amazingly sweet!!

    This should help a ton for my current WP7 project which I am currently developing at 1280 X 720.

    I was dreading resizing all of my textures for 800 X 480 landscape WP7 mode.

    Thanks for posting this.

    Allan Chaney

  • You know what.. Can you please moderate my previous post and delete it? I made a stupid mistake. I switched SetVirtualResolution and SetResolution by accident. Your class works fine. And it’s great! No modifications needed nothing.

  • Hello,

    Nice article, but it left me wondering on two points, maybe you could clear things up.

    1) I was wondering if by using this approach, wouldn’t it also be required to transform whatever mouse coordinates you receive from Mouse.GetState() with the inverse of the resolution matrix, in order to get it back into “game space” and be able to do picking or anything you need?

    2) And what if your SpriteBatch is already using a transformation matrix (such as for camera transformations). Would multiplying them together be enough (and is the order of multiplication relevant here?)

    Thanks,
    David Gouveia

  • This is so great. Thanks man. Especially for sharing the complete class 🙂
    Best Wishes from Germany
    Daniel

  • Hey mate, I thought I should let you know that this sample needs a little update to work properly in XNA4.

    That’s because starting from XNA4 doing a GraphicsDevice.Clear will always clear the entire backbuffer no matter what Viewport you’ve set!

    This implies two things.

    1) You no longer need to reset to the full Viewport before clearing to black. You just call GraphicsDevice.Clear(). This also means that it’s no longer necessary to switch between the full and virtual Viewport constantly – just keep it always in the virtual Viewport.

    2) You need to use SpriteBatch instead to fill *only* the virtual Viewport with the desired color. Simple 1×1 texture drawn to fill the Viewport’s rectangle should do.

  • Yes it works, but since the second clear (after setting the virtual viewport again) also fills the entire screen, you lose the pillarbox/letterbox effect.

    And unfortunately filling with a quad is somewhat slower than the original Clear :\

  • I didn’t explain myself properly. What I mean is that even though the pillarbox will still be there and work properly, if you fill everything with black you won’t be able to “see them”.

    In a normal game you’re usually filling the entire viewport with your own graphics, so this is not a problem.

    But if you want the XNA sample appearance of having a CornflowerBlue background, then obviously clearing it to black won’t work. In that case, if you want to retain a certain background color, you have to replace the second Clear it with a SpriteBatch.Draw call to actually see it.

    In your 3.1 version of this article, you’d clear to Black and then clear to CornflowerBlue, so even if nothing was drawn you’d see the difference between viewports. That won’t work now.

  • Any way to get this to work on 3D models as well?

    When I go to fullscreen the models are always stretched but I have yet to find a way to use Resolution.GetMatrix to fix it.

  • I need some help here.

    The following code doesn’t work in XNA 4.0:

    spriteBatch.Begin(SpriteBlendMode.AlphaBlend,SpriteSortMode.Immediate,SaveStateMode.SaveState,Resolution.getTransformationMatrix());

    So I changed it to this:

    spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, Resolution.getTransformationMatrix());

    But then I get this error:

    “No overload for method ‘Begin’ takes 3 arguments”

    Any help would be appreciated! Also, I’m still a bit new to XNA, so an explaination would be nice too. 🙂

  • I’ve been working on a project using the game state management sample code from xna website and have been trying to incorporate this into my game. Have got the sample working fine and i can change resolutions fine however when i implemented it into my project everything is drawn at the top of the screen. It all resizes properly but it’s not centred.
    This is there i set the resolution in my game class

    graphics = new GraphicsDeviceManager(this);
                Resolution.Init(ref graphics);
                Resolution.SetVirtualResolution(1280, 720);
                screenManager = new ScreenManager(this);
     
                Components.Add(screenManager);
     
                // Activate the first screens.
                screenManager.AddScreen(new SplashScreen(), null);
            }
            protected override void Update(GameTime gameTime)
            {
                if (OptionsMenuScreen.currentResolution == 0)
                {
                    Resolution.SetResolution(800, 600, false);
                    OptionsMenuScreen.CurrentWidth = 800;
                    OptionsMenuScreen.CurrentHeight = 600;
                    graphics.ApplyChanges();
                }
                if (OptionsMenuScreen.currentResolution == 1)
                {
                    Resolution.SetResolution(1280, 720, false);
                    OptionsMenuScreen.CurrentWidth = 1280;
                    OptionsMenuScreen.CurrentHeight = 720;
                    graphics.ApplyChanges();
                }
                if (OptionsMenuScreen.currentResolution == 2)
                {
                    Resolution.SetResolution(1920, 1080, false);
                    OptionsMenuScreen.CurrentWidth = 1920;
                    OptionsMenuScreen.CurrentHeight = 1080;
                    graphics.ApplyChanges();
                }
                if (OptionsMenuScreen.currentResolution == 3)
                {
                    Resolution.SetResolution(2048, 1536, false);
                    OptionsMenuScreen.CurrentWidth = 1048;
                    OptionsMenuScreen.CurrentHeight = 1536;
                    graphics.ApplyChanges();
                }

    Have changes _Width and _Height in Resolutions to

            static private int _Width =OptionsMenuScreen.CurrentWidth;
            static private int_Height=OptionsMenuScreen.CurrentHeight;

    And this is how i’m drawing my sprites

     spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend,null,null,null,null, Resolution.getTransformationMatrix());
               spriteBatch.Draw(Background, Vector2.Zero, Color.White);

    Any Help would be greatly appreciated.

  • Stephen,

    Not sure if you’re still having issues with this but my guess would be that it is drawing the background image in the top left corner. This is because you are using the

    public void Draw (
             Texture2D texture,
             Vector2 position,
             Color color
    )

    method which draws the texture by mapping the default texture “origin” to the given vector. Unless otherwise specified in the Draw method, the default texture “origin” is at (0,0) in sprite coordinates which happens to be the very middle of the sprite and you are telling it to draw this at (0, 0) in screen coordinates which is the very top left corner of your screen.

    You can fix this two ways.

    The easiest would be to set your position to the middle of the viewport instead ( viewport width / 2, viewport height / 2).

    Or you can use the following Draw method

    public void Draw (
             Texture2D texture,
             Vector2 position,
             Nullable sourceRectangle,
             Color color,
             float rotation,
             Vector2 origin,
             float scale,
             SpriteEffects effects,
             float layerDepth
    )

    to manipulate the origin position of your sprite.

  • Hi David, I’m a having a conflict using the last parameter of the Draw method (the matrix transformation). Normally I use the matrix to translate the camera across the level and I want to know if there’s a way to “mix” the two matrices.

    This is my matrix transformation and my current Draw method:

                     Matrix cameraTransform = Matrix.CreateTranslation(-cameraPosition, 0.0f, 0.0f);
                    spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend, SamplerState.LinearClamp, DepthStencilState.Default,
                                      RasterizerState.CullCounterClockwise, null, cameraTransform);

    Thanks in advance.

  • I think there may be an issue. Here is the code I have in your constructor for Game1
    pre lang=”cpp”> Resolution.SetVirtualResolution(1024, 768);
    Resolution.SetResolution(1280, 1024, true);

    To test out your code i put this in the game1 update method:

     if (GamePad.GetState(PlayerIndex.One).Buttons.A == ButtonState.Pressed)
                {
                    Resolution.SetResolution(1280, 1024, true);
                }
                else if (GamePad.GetState(PlayerIndex.One).Buttons.B == ButtonState.Pressed)
                {
                    Resolution.SetResolution(1920, 1080, true);
                }

    When i use this, the image does not keep its aspect ratio, and stretches horizontally. I also noticed something odd in your RecreateScaleMatrix() function:

     _ScaleMatrix = Matrix.CreateScale(
                               (float)_Device.GraphicsDevice.Viewport.Width / _VWidth,
                               (float)_Device.GraphicsDevice.Viewport.Width / _VWidth,
                               1f);

    Shouldn’t height/vheight come into play somewhere?

    Any help would be appreciated, your code has been invaluable. Thanks

  • Nevermind, figured it out. I was just setting a full screen resolution that had a different aspect ratio than my monitor. Got ahead of myself and made a simple mistake.

  • I read that on another post but I don’t know how to calculate ViewportX, ViewportY and Matrix.

    Thanks David

  • First of all I would like to thank you for the sample code, it has been very helpful.

    When I set the resolution to the max one my graphics cards supports and drag the game window then I get an error. The error is on the line of code where the Viewport is set. The error is: “The viewport is invalid. The viewport cannot be larger than or outside of the current render target bounds. The MinDepth and MaxDepth must be between 0 and 1.”

    This is in XNA 4.0. Any idea what might be going wrong?

    Cheers,
    poohshoes

    some additional info:
    When the app breaks Viewport is set at 1920×1059 for some bizzare reason.
    It is trying to set the Viewport to 1920×1080.

    I have put a breakpoint in the app where the view is happily 1920×1080.

  • I know this is an old post, but using monogame this line gives me an error:
    _Device.GraphicsDevice.Viewport = vp;
    System.NullReferenceExeption: object reference not set to an instance of an object.

  • Hi David – awesome post! This information has helped me ship “Candy Kid” [http://bit.ly/1OngOgj] to numerous devices (iOS / Android) and all rendering great regardless of the target resolution – so major thanks for this code sample.

    @epse – I also used MonoGame (v3.5) and all worked ok for me.
    I use the default XNA code to construct the GraphicsDeviceManager, and pass this to initialize the _Device before invoke BeginDraw();

    @Pablo – here is the psuedo code that I wrote to convert the mouse coordinates and touches too:

    Vector2 touchPosition = null;
    Vector2 viewport = “Resolution.Viewport”;
    TouchLocation location = null;

    TouchCollection touchCollection = TouchPanel.GetState();
    if (touchCollection.Count > 0)
    {
    location = touchCollection[0];
    TouchLocation touchLocation = (TouchLocation)location;

    Vector2 deltaPosition = touchLocation.Position – viewport;

    Matrix transformationMatrix = Matrix.CreateScale((float)viewport.Width / _VWidth, (float)viewport.Width / _VWidth, 1f);
    Matrix invertTransformationMatrix = Matrix.Invert(transformationMatrix);

    touchPosition = Vector2.Transform(deltaPosition, invertTransformationMatrix);
    }

Leave a Reply

Your email address will not be published. Required fields are marked *