JSEDLAK » Blog Archive » FGF: A Helper For Creating Render Targets

FGF: A Helper For Creating Render Targets

Before I begin this post, big thanks to Eibx and David over at the Community Forums for helping me find these methods. I have modified the CheckTexture method a bit, but its purpose remains unchanged.

As of the last FGF article, the Application class was implementing the IGame interface but was missing the ability to create a render target object on the PC and Xbox 360. For PC games this can be a troubling problem since different hardware can obviously require different formats and dimensions of render target. Rather than bake this functionality into the Application class itself, it is moved to a static helper class so that all developers can make good use of its functionality at any point in time.

To start off, a simple default creation method is included to give the basic functionality an easy access point.

?View Code CSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
using System;
 
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
 
namespace FocusedGames.Xna.Graphics
{
    public static class GraphicsHelper
    {
        public static RenderTarget2D CreateRenderTarget(GraphicsDevice device)
        {
            return CreateRenderTarget(
                device, 
                device.PresentationParameters.BackBufferWidth, 
                device.PresentationParameters.BackBufferHeight, 
                1, 
                device.PresentationParameters.BackBufferFormat
            );
        }

Secondly the real CreateRenderTarget method is revealed. It checks the device format as well as the multisample type to ensure that they are in accordance with what the hardware device requires.

?View Code CSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
        public static RenderTarget2D CreateRenderTarget(GraphicsDevice device, int width, int height, int levels, SurfaceFormat format)
        {
            MultiSampleType sampleType = device.PresentationParameters.MultiSampleType;
 
            if (
                !GraphicsAdapter.DefaultAdapter.CheckDeviceFormat(
                    DeviceType.Hardware, 
                    GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Format, 
                    TextureUsage.None, 
                    QueryUsages.None, 
                    ResourceType.RenderTarget, 
                    format
                )
            )
            {
                format = device.DisplayMode.Format;
            }
            else if (
                !GraphicsAdapter.DefaultAdapter.CheckDeviceMultiSampleType(
                    DeviceType.Hardware, 
                    format, 
                    device.PresentationParameters.IsFullScreen, 
                    sampleType
                )
            )
            {
                sampleType = MultiSampleType.None;
            }
 
            CheckTextureSize(width, height, out width, out height);
 
            return new RenderTarget2D(device, width, height, levels, format, sampleType, 0);
        }

This method makes a call to the CheckTextureSize method which the MSDN Documentation is missing. Similar to the above method, the CheckTextureSize function checks the width and the height parameters against what the graphics adapter supports and, if necessary, changes each to comply with the requirements.

?View Code CSHARP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public static bool CheckTextureSize(int width, int height, out int newWidth, out int newHeight)
        {
            bool returnValue = false;
 
            GraphicsDeviceCapabilities caps = GraphicsAdapter.DefaultAdapter.GetCapabilities(DeviceType.Hardware);
 
            if (caps.TextureCapabilities.RequiresPower2)
            {
                returnValue = true;
 
                // Perform a simple check before doing (possibly) unecessary math
                if (!IsPowerOfTwo(width))
                {
                    // Find the nearest base two log of the current width,
                    // and go up to the next integer
                    double exponent = Math.Ceiling(Math.Log(width) / Math.Log(2));
 
                    width = (int)Math.Pow(2, exponent);
                }
 
                // Perform a simple check before doing (possibly) unecessary math
                if (!IsPowerOfTwo(height))
                {
                    // Find the nearest base two log of the current height,
                    // and go up to the next integer
                    double exponent = Math.Ceiling(Math.Log(height) / Math.Log(2));
 
                    height = (int)Math.Pow(2, exponent);
                }
            }
 
            if (caps.TextureCapabilities.RequiresSquareOnly)
            {
                returnValue = true;
 
                width = Math.Max(width, height);
 
                height = width;
            }
 
            newWidth = Math.Min(caps.MaxTextureWidth, width);
            newHeight = Math.Min(caps.MaxTextureHeight, height);
 
            return returnValue;
        }

The only real change I have introduced here is a simple check to see if the width and height are already powers of two before doing some possibly unecessary math. I decided to keep the check because it is extremely basic in terms of mathematical computation.

?View Code CSHARP
1
2
3
4
5
6
        public static bool IsPowerOfTwo(int number)
        {
            return (number & (number - 1)) == 0;
        }
    }
}

And there you have it! A robust way for creating a render target on all the platforms that XNA supports. Now that we have this, the last change needs to happen in the Application class. Remember that ResetRenderTarget method? Well now it can be fixed up properly:

?View Code CSHARP
1
2
3
4
5
6
7
protected virtual void ResetRenderTarget()
{
    if (renderTarget != null && !renderTarget.IsDisposed)
        renderTarget.Dispose();
 
    renderTarget = GraphicsHelper.CreateRenderTarget(GraphicsDevice, (int)orientedDisplaySize.X, (int)orientedDisplaySize.Y, 1, SurfaceFormat.Color);
}

Leave a Reply