Render Targets
Implementation status: RenderTarget2D is fully implemented on the EasyGL and Vulkan backends. Multiple render targets (MRT) are supported on both. RenderTargetCube is implemented on EasyGL. The Graphics namespace is approximately
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 backends: 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 |
Backend implementation
| Backend | Colour attachment | Depth attachment | Status |
|---|---|---|---|
| EasyGL (OpenGL ES 3.0 / WebGL 2) | FBO + GL_TEXTURE_2D |
Depth renderbuffer (GL_RENDERBUFFER) |
Implemented |
| Vulkan | Off-screen VkImage + VkImageView |
Depth VkImage attachment in VkRenderPass |
Implemented |
| bgfx | bgfx framebuffer texture | — | compiles/links |
| SDL_Renderer | SDL_Texture with target flag |
— | not implemented |
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 backends.
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)
The EasyGL and Vulkan backends support rendering simultaneously to up to four colour targets in a single draw call. 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.
// Create G-buffer targets (albedo, normals, depth-as-colour)
RenderTarget2D rtAlbedo (graphicsDevice, 1920, 1080, SurfaceFormat::Color,
DepthFormat::Depth24Stencil8, 0,
RenderTargetUsage::DiscardContents);
RenderTarget2D rtNormals(graphicsDevice, 1920, 1080, SurfaceFormat::Rgba1010102,
DepthFormat::None, 0,
RenderTargetUsage::DiscardContents);
RenderTarget2D rtEmissive(graphicsDevice, 1920, 1080, SurfaceFormat::HalfVector4,
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 limit: Up to 4 simultaneous colour attachments are supported on EasyGL and Vulkan. The bgfx backend supports MRT in principle but has not been tested. SDL_Renderer does not support MRT.
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 backends. |
Common use cases
- Shadow maps — render the scene from the light's point of view into a depth-only
RenderTarget2D(DepthFormat::Depth24,SurfaceFormat::Single), then sample it in the main pass to compute shadow factors. - 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
RenderTargetCubeto capture the surroundings at runtime and feed it intoEnvironmentMapEffectfor 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::Rgba1010102, DepthFormat::None, 0,
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();