Render Targets

Microsoft::Xna::Framework::Graphics — RenderTarget2D, RenderTargetCube, off-screen rendering

Implementation status: RenderTarget2D is implemented on EasyGL and Vulkan, which also support multiple render targets (MRT); RenderTargetCube is implemented on EasyGL. Render targets are also real on SDL_GPU, DIRECTX9 and DIRECTX11. Two limitations are worth knowing before you design around them: the WebGPU renderer has no render-target support at all, and DIRECTX12 clears an MRT but never draws to it. Format admission is renderer-qualified: Skia has a broad promoted subset, IGL promotes Rg32/Single, and other families use the framework's Color-only rule. See Renderers for the full per-renderer picture.

Overview

RenderTarget2D is a subclass of Texture2D in the Microsoft::Xna::Framework::Graphics namespace. It can be bound as the active render target so that subsequent draw calls write their colour (and optionally depth) output into the texture rather than the back buffer. Because it inherits from Texture2D, the same object can be passed directly to SpriteBatch.Draw() or bound to an effect sampler — no explicit copy or conversion is required.

The XNA render-target model maps cleanly onto both the EasyGL and Vulkan renderers: EasyGL implements render targets as OpenGL Framebuffer Objects (FBOs) with an attached depth renderbuffer, and Vulkan implements them as off-screen Vulkan images with a depth attachment and an explicit VkRenderPass.

Constructor

The minimal constructor takes a GraphicsDevice, a width, and a height. An optional extended form accepts surface format, depth format, multisample count, and usage hint:

// Minimal — RGBA8 colour, Depth24Stencil8, no MSAA
RenderTarget2D rt(graphicsDevice, 1024, 768);

// Extended form
RenderTarget2D rt(
    graphicsDevice,
    1024, 768,
    SurfaceFormat::Color,          // preferredFormat
    DepthFormat::Depth24Stencil8,  // preferredDepthFormat
    0,                             // preferredMultiSampleCount (0 = no MSAA)
    RenderTargetUsage::DiscardContents
);
Parameter Type Default Description
graphicsDevice GraphicsDevice& The device that owns this render target
width, height int Dimensions in texels
preferredFormat SurfaceFormat Color Colour attachment surface format
preferredDepthFormat DepthFormat Depth24Stencil8 Depth (and optional stencil) format; use None to omit depth
preferredMultiSampleCount int 0 MSAA sample count; 0 disables multisampling
usage RenderTargetUsage DiscardContents Whether prior contents are preserved between frames

Renderer implementation

Renderer Colour attachment Depth attachment Status
EasyGL (OpenGL ES 3.0 / WebGL 2) FBO + GL_TEXTURE_2D Depth renderbuffer (GL_RENDERBUFFER) Real, incl. MRT
Vulkan Off-screen VkImage + VkImageView Depth VkImage attachment in VkRenderPass Real; MRT proxy, MSAA resolve
DIRECTX9 (Windows-only) Off-screen surface in D3DPOOL_DEFAULT Depth-stencil surface Real; caps-validated MRT
DIRECTX11 (Windows-only) Render-target view over a texture Depth-stencil view Real; MRT with deferred resolve
SDL_GPU SDL_GPU texture used as a colour target Depth-stencil target Real; stencil + MSAA resolve
DIRECTX12 (Windows-only) Render-target view Depth-stencil view MRT is cleared but never drawn to
WebGPU No render-target support at all
Headless Renders nothing by design

The remaining renderers — SDL_Renderer, Direct2D, Canvas, HTML_DOM, Skia, Blend2D, FreeDirect, GDI, SVG_DOM and OpenVG (all 2D-only), plus Software and bgfx — are not characterised for render-target behaviour by the current audit, so do not assume off-screen rendering works on them without testing. WebGPU is a backbuffer-only forward renderer: it has no render targets, no MSAA and no cube/3D textures, so every technique on this page is unavailable there. The Headless renderer deliberately renders nothing at all; its ReadBackbuffer returns the last clear colour. See Renderers for the full picture across all 50 renderers.

Color is the portable choice, not the only alpha.1 choice. Skia promotes a broad verified render-target subset and IGL promotes Rg32 and Single. Other families defer to the framework's Color-only rule. Every example on this page uses SurfaceFormat::Color because it is the common denominator.

Setting the render target

Call graphicsDevice.SetRenderTarget() before any draw calls you want redirected to the texture. All subsequent draws — including SpriteBatch draws, 3D primitive draws, and model draws — write to the render target until a different target (or nullptr) is set.

// Redirect output to the render target
graphicsDevice.SetRenderTarget(renderTarget);

// Clear the off-screen surface
graphicsDevice.Clear(Color::CornflowerBlue);

// Draw whatever you need into the render target
spriteBatch.Begin();
spriteBatch.Draw(myTexture, Vector2::Zero, Color::White);
spriteBatch.End();

Restoring the back buffer

Pass nullptr (or an equivalent null render target) to restore rendering to the default back buffer. After this call the render target texture is complete and ready to be sampled.

// Restore default back buffer
graphicsDevice.SetRenderTarget(nullptr);

// The render target is now a fully populated texture — draw it to the screen
spriteBatch.Begin();
spriteBatch.Draw(renderTarget, Vector2::Zero, Color::White);
spriteBatch.End();

Always restore the back buffer before calling GraphicsDevice.Present(). Drawing to the screen while a non-null render target is bound is undefined behaviour and will silently produce no output on most renderers.

Using the result as a texture

Because RenderTarget2D inherits from Texture2D, you can pass it anywhere a Texture2D is accepted — no copy, readback, or conversion is required. This makes it easy to chain render passes:

// renderTarget is-a Texture2D — use it directly with SpriteBatch
spriteBatch.Draw(renderTarget, screenRect, Color::White);

// Or bind it to a 3D effect sampler
basicEffect.SetTexture(renderTarget);
basicEffect.SetTextureEnabled(true);

Multiple Render Targets (MRT)

MRT is real on EasyGL, Vulkan, DIRECTX9 (caps-validated) and DIRECTX11 (with a deferred resolve). Pass a collection of render targets to SetRenderTargets(). Each target must have the same dimensions. In the fragment shader, output values are written to each attachment in order.

Two caveats: DIRECTX12 clears an MRT but never draws to it, so a G-buffer pass silently produces nothing there; and most renderer families only admit SurfaceFormat::Color, so portable G-buffers must encode into RGBA8. Skia and IGL have explicit promoted exceptions, not a framework-wide guarantee.

// Create G-buffer targets (albedo, normals, emissive)
// NOTE: all attachments must be SurfaceFormat::Color — CNA cannot
// construct Rgba1010102 / HalfVector4 / any other format today.
RenderTarget2D rtAlbedo (graphicsDevice, 1920, 1080, SurfaceFormat::Color,
                          DepthFormat::Depth24Stencil8, 0,
                          RenderTargetUsage::DiscardContents);
RenderTarget2D rtNormals(graphicsDevice, 1920, 1080, SurfaceFormat::Color,
                          DepthFormat::None, 0,
                          RenderTargetUsage::DiscardContents);
RenderTarget2D rtEmissive(graphicsDevice, 1920, 1080, SurfaceFormat::Color,
                           DepthFormat::None, 0,
                           RenderTargetUsage::DiscardContents);

// Bind all three at once — geometry pass writes to all simultaneously
graphicsDevice.SetRenderTargets({rtAlbedo, rtNormals, rtEmissive});
graphicsDevice.Clear(Color::Black);

// ... draw geometry here ...

// Restore back buffer for the lighting pass
graphicsDevice.SetRenderTarget(nullptr);

MRT support: real on EasyGL, Vulkan, DIRECTX9 and DIRECTX11. DIRECTX12 clears an MRT but never draws to it. WebGPU has no render targets at all. The 2D-only renderers and bgfx are not characterised for MRT — test before relying on them.

RenderTargetCube

RenderTargetCube is a cube-map render target used primarily for dynamic environment mapping and reflections. It exposes six faces via the CubeMapFace enum: PositiveX, NegativeX, PositiveY, NegativeY, PositiveZ, NegativeZ. Each face must be rendered in a separate pass with the camera oriented toward that face.

// Create a 512x512 cube-map render target
RenderTargetCube rtCube(graphicsDevice, 512,
                         SurfaceFormat::Color,
                         DepthFormat::Depth24Stencil8,
                         0,
                         RenderTargetUsage::DiscardContents);

// Render each face
for (int face = 0; face < 6; ++face) {
    graphicsDevice.SetRenderTarget(rtCube,
        static_cast<CubeMapFace>(face));
    graphicsDevice.Clear(Color::Black);

    // Draw the scene from the cube face's view direction
    DrawSceneForFace(static_cast<CubeMapFace>(face));
}

// Restore back buffer and bind the cube map to an effect
graphicsDevice.SetRenderTarget(nullptr);
environmentMapEffect.SetEnvironmentMap(rtCube);

DepthFormat enum

Value Description
DepthFormat::None No depth or stencil attachment. Use for colour-only targets such as off-screen UI layers or G-buffer colour channels.
DepthFormat::Depth16 16-bit depth buffer. Lower memory footprint; reduced precision at large distances.
DepthFormat::Depth24 24-bit depth buffer. Standard precision for most 3D rendering.
DepthFormat::Depth24Stencil8 24-bit depth + 8-bit stencil buffer. Required for stencil operations such as shadow volumes and portal rendering.

RenderTargetUsage enum

Value Description
RenderTargetUsage::DiscardContents Default. Contents are considered undefined after SetRenderTarget(nullptr) until the next explicit Clear(). Allows tile-based GPUs to avoid flushing attachment data, improving mobile performance.
RenderTargetUsage::PreserveContents Contents are guaranteed to be preserved across render target switches. Use when you need to read back previous frame data without an explicit copy.
RenderTargetUsage::PlatformContents Defers to the platform's default behaviour. Equivalent to DiscardContents on most modern renderers.

Common use cases

  • Shadow maps — render the scene from the light's point of view into a RenderTarget2D (DepthFormat::Depth24), then sample it in the main pass to compute shadow factors. Note that the colour attachment must still be SurfaceFormat::Color, so a single-channel float depth target is not available; pack depth into RGBA8 instead.
  • Post-processing effects — render the full scene into a render target, then apply screen-space effects (blur, bloom, colour grading, FXAA) in one or more additional passes before presenting.
  • Minimap rendering — render a top-down view of the game world into a small render target each frame and display it as a HUD element via SpriteBatch.
  • Blur and bloom passes — chain multiple render targets: render → horizontal blur → vertical blur → composite back onto the main scene.
  • Deferred shading G-buffer — use MRT to write albedo, normals, and material properties simultaneously, then run a screen-space lighting pass reading from all three.
  • Dynamic environment mapping — use RenderTargetCube to capture the surroundings at runtime and feed it into EnvironmentMapEffect for real-time reflections.

Code example 1 — basic render-to-texture

Render a scene into a texture, then display that texture scaled to the screen.

// --- Setup (LoadContent) ---
auto renderTarget = std::make_shared<RenderTarget2D>(
    *graphicsDevice, 512, 512);

// --- Per-frame (Draw) ---

// 1. Render scene into the render target
graphicsDevice->SetRenderTarget(*renderTarget);
graphicsDevice->Clear(Color::CornflowerBlue);

spriteBatch->Begin();
spriteBatch->Draw(sceneTexture, Vector2::Zero, Color::White);
spriteBatch->End();

// 2. Restore back buffer
graphicsDevice->SetRenderTarget(nullptr);
graphicsDevice->Clear(Color::Black);

// 3. Display the captured texture on screen
Rectangle destRect(0, 0,
    graphicsDevice->Viewport().Width,
    graphicsDevice->Viewport().Height);

spriteBatch->Begin();
spriteBatch->Draw(*renderTarget, destRect, Color::White);
spriteBatch->End();

Code example 2 — post-processing pipeline

A two-pass blur: render the scene, apply a horizontal blur, apply a vertical blur, then composite to screen.

// --- Setup ---
auto rtScene  = std::make_shared<RenderTarget2D>(*graphicsDevice, 1920, 1080);
auto rtBlurH  = std::make_shared<RenderTarget2D>(*graphicsDevice, 1920, 1080);
auto rtBlurV  = std::make_shared<RenderTarget2D>(*graphicsDevice, 1920, 1080);

auto blurHEffect = content->Load<ShaderEffect>("shaders/blur_horizontal");
auto blurVEffect = content->Load<ShaderEffect>("shaders/blur_vertical");

// --- Per-frame ---

// Pass 1: render the scene
graphicsDevice->SetRenderTarget(*rtScene);
graphicsDevice->Clear(Color::Black);
DrawScene();

// Pass 2: horizontal blur
graphicsDevice->SetRenderTarget(*rtBlurH);
blurHEffect->Parameters["SourceTexture"]->SetValue(*rtScene);
blurHEffect->CurrentTechnique->Passes[0]->Apply();
DrawFullscreenQuad();

// Pass 3: vertical blur
graphicsDevice->SetRenderTarget(*rtBlurV);
blurVEffect->Parameters["SourceTexture"]->SetValue(*rtBlurH);
blurVEffect->CurrentTechnique->Passes[0]->Apply();
DrawFullscreenQuad();

// Pass 4: composite to back buffer
graphicsDevice->SetRenderTarget(nullptr);
graphicsDevice->Clear(Color::Black);

spriteBatch->Begin();
spriteBatch->Draw(*rtBlurV, fullscreenRect, Color::White);
spriteBatch->End();

Code example 3 — MRT g-buffer setup

A minimal deferred rendering geometry pass writing albedo and normals simultaneously.

// --- Setup ---
auto rtAlbedo  = std::make_shared<RenderTarget2D>(
    *graphicsDevice, 1920, 1080,
    SurfaceFormat::Color, DepthFormat::Depth24Stencil8, 0,
    RenderTargetUsage::DiscardContents);

auto rtNormals = std::make_shared<RenderTarget2D>(
    *graphicsDevice, 1920, 1080,
    SurfaceFormat::Color, DepthFormat::None, 0,  // portable across renderer families
    RenderTargetUsage::DiscardContents);

auto gBufferEffect = content->Load<ShaderEffect>("shaders/gbuffer_write");

// --- Geometry pass ---
graphicsDevice->SetRenderTargets({*rtAlbedo, *rtNormals});
graphicsDevice->Clear(Color::Black);

// The fragment shader writes:
//   layout(location = 0) out vec4 outAlbedo;
//   layout(location = 1) out vec4 outNormal;
gBufferEffect->CurrentTechnique->Passes[0]->Apply();
DrawGeometry();

// --- Lighting pass ---
graphicsDevice->SetRenderTarget(nullptr);
graphicsDevice->Clear(Color::Black);

auto lightingEffect = content->Load<ShaderEffect>("shaders/deferred_lighting");
lightingEffect->Parameters["AlbedoMap"] ->SetValue(*rtAlbedo);
lightingEffect->Parameters["NormalMap"] ->SetValue(*rtNormals);
lightingEffect->CurrentTechnique->Passes[0]->Apply();
DrawFullscreenQuad();