Render Targets
Implementation status: at snapshot c1c316b9, RenderTarget2D is implemented on every 3D renderer — the three EasyGL identities, VULKAN, WEBGPU, SDL_GPU, DIRECTX9/11, FNA3D, SOFTWARE and METAL — and in a Color-only, depth-less form on the 2D-only SDL_RENDERER. RenderTargetCube has real storage on the same 3D renderers. Multiple render targets (MRT) are real on 11 identities, but MRT, float and HDR formats, larger cubes and 3D textures require GraphicsProfile::HiDef: the default profile is Reach, and it is now enforced on every renderer. See the profile limits, and 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 every 3D renderer: EasyGL implements render targets as OpenGL Framebuffer Objects (FBOs) with an attached depth renderbuffer, Vulkan as off-screen Vulkan images with a depth attachment and an explicit VkRenderPass, and the Direct3D renderers as render-target views over textures. A RenderTarget2D is not copyable (it can be moved), so hold it in a std::unique_ptr or as a member.
Reach and HiDef profile limits
The default GraphicsProfile is Reach, and this snapshot enforces it on every renderer (alpha.1 enforced Reach ceilings only on DIRECTX9). Several render-target features throw, or quietly degrade to Color, unless you ask for HiDef before Initialize() applies your preferences to the device:
| Rule | Reach (default) | HiDef |
|---|---|---|
RenderTarget2D edge length | 2048 | 4096 |
RenderTargetCube edge length | 512, power of two only | 4096 |
Simultaneous render targets (SetRenderTargets) | 1 | 4 (also the global cap) |
Render-target SurfaceFormat | Colour formats only (Color, Bgr565, Bgra5551, Bgra4444, NormalizedByte2/4); a float, HDR, Rgba1010102, Rg32, Rgba64 or Alpha8 request silently becomes Color | All XNA formats the renderer reports renderable (never DXT); a request the renderer cannot honour still becomes Color |
Aspect ratio of a RenderTarget2D | At most 2048 : 1 in both profiles | |
Texture3D, OcclusionQuery, 32-bit index buffers, GetBackBufferData | refused | allowed (where the renderer supports them) |
Ask for HiDef in your game's constructor, before Initialize() applies your preferences to the device (or call CNA::SetProjectGraphicsProfileEXT(GraphicsProfile::HiDef) before the Game is created):
MyGame() : graphics_(this)
{
graphics_.setGraphicsProfileProperty(GraphicsProfile::HiDef);
}
The full list of what the profile gates, with the exact exception messages, is Tutorial 152: Reach vs HiDef. Because the format fallback is silent, do not assume you got the format you asked for: query it. GraphicsDevice::SupportsSurfaceFormatAsRenderTargetEXT(format) answers with the profile rule and the renderer's own verdict, and GraphicsDevice::SupportsCapability(GraphicsCapability::MultipleRenderTargets) is false under Reach on every renderer.
Constructor
The minimal constructor takes a GraphicsDevice, a width, and a height, and creates a Color target without a depth buffer. The extended form adds a mip-map flag, surface format, depth format, multisample count, and usage hint:
// Minimal — RGBA8 colour, DepthFormat::None, no mip chain, no MSAA
RenderTarget2D rt(graphicsDevice, 1024, 768);
// Extended form
RenderTarget2D rt2(
graphicsDevice,
1024, 768,
false, // mipMap
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; limited by the profile (2048 Reach, 4096 HiDef) and by a 2048 : 1 aspect ratio |
mipMap |
bool |
false |
Allocates a full mip chain (extended form only; not supported on the 2D-only SDL_RENDERER) |
preferredFormat |
SurfaceFormat |
Color |
Colour attachment surface format; falls back to Color when the profile or renderer cannot honour it |
preferredDepthFormat |
DepthFormat |
None (minimal constructor) |
Depth (and optional stencil) format; None omits depth. Use Depth24Stencil8 when you need stencil |
preferredMultiSampleCount |
int |
0 |
MSAA sample count, rounded down to a power of two (1 becomes 0); 0 disables multisampling; the applied count is device-clamped |
usage |
RenderTargetUsage |
DiscardContents |
Whether prior contents are preserved between frames |
Renderer implementation
| Renderer | Colour attachment | Depth attachment | Status |
|---|---|---|---|
EasyGL: OPENGLES3, OPENGL33, WEBGL2 |
FBO + GL_TEXTURE_2D |
Depth renderbuffer (GL_RENDERBUFFER) |
Real, incl. MRT (up to 4) and cube faces in an MRT set |
VULKAN |
Off-screen VkImage + VkImageView |
Depth VkImage attachment in VkRenderPass |
Real; MRT when maxColorAttachments > 1, MSAA resolve, per-format probing |
WEBGPU |
WebGPU texture, RENDER_ATTACHMENT |
Depth attachment | Real; MRT of 2–4 RenderTarget2Ds, 4x MSAA when the probe passes; a cube face inside an MRT set is refused |
DIRECTX9 (Windows-only) |
Off-screen surface in D3DPOOL_DEFAULT |
Depth-stencil surface | Real; MRT validated against device caps; a cube face inside an MRT set is refused |
DIRECTX11 (Windows-only) |
Render-target view over a texture | Depth-stencil view | Real; MRT; a cube face inside an MRT set is refused |
SDL_GPU |
SDL_GPU texture used as a colour target | Depth-stencil target | Real; four independently writable, mixed-format colour targets; MSAA resolve; cube faces inside an MRT set refused |
FNA3D |
FNA3D render-target texture | FNA3D depth-stencil buffer | Real, incl. MRT and cube faces in an MRT set; no float render-target formats through the public API (Color only) |
SOFTWARE |
CPU colour buffer | CPU depth/stencil buffer | Real, incl. 4 targets, cubes, float formats and 4x MSAA (CPU) |
METAL (macOS, iOS) |
Metal texture | Depth-stencil texture (only the planes the depth format names; Depth16 native) |
Real 2D and cube targets in Color and the float, half and wide formats; MRT up to 8 targets, cube faces included; MSAA (2x/4x on an M4), resolved at every encoder boundary; a multisampled cube refuses SetData |
HEADLESS |
— | — | Records and validates draws; renders nothing by design |
STUB |
— | — | No render targets (construction succeeds, binding throws NotSupportedException) |
SDL_RENDERER |
Backend off-screen surface | None | 2D targets for SpriteBatch work: Color only, request DepthFormat::None, no mip chain, no MSAA |
SDL_RENDERER is 2D-only by design. Its off-screen targets serve SpriteBatch work such as a minimap or a UI cache; the 3D techniques on this page (depth, MRT, cube targets, float formats) are unavailable there. It throws when you construct a target with a depth format, a mip chain or multisampling, and it refuses a multi-target set or a cube face, so use the minimal constructor there.
Formats
Color is the portable choice, not the only choice. Format admission is a two-step rule: first the GraphicsProfile gate (Reach refuses the float and HDR formats), then the renderer's verdict. Seven families classify formats themselves — DIRECTX11 (all XNA formats the device reports renderable), VULKAN (per-format device properties), the EasyGL identities (probed against the live context), SDL_GPU (float and half render targets exist), WEBGPU (probed; Rgba64 needs an optional feature), SOFTWARE (all seven float/half formats) and METAL (Rgba1010102, Rg32, Rgba64 and every float and half format). A renderer that does not classify falls back to the framework rule, which admits Color only; FNA3D, DIRECTX9, HEADLESS and the 2D-only SDL_RENDERER are in that group for render targets. Most examples on this page use SurfaceFormat::Color because it is the common denominator.
// Ask, do not assume: profile rule AND renderer verdict.
SurfaceFormat hdr = SurfaceFormat::HdrBlendable;
if (!graphicsDevice.SupportsSurfaceFormatAsRenderTargetEXT(hdr))
hdr = SurfaceFormat::Color; // what the RenderTarget2D constructor would have picked anyway
RenderTarget2D scene(graphicsDevice, 1280, 720, false, hdr, DepthFormat::Depth24);
Setting the render target
Call graphicsDevice.SetRenderTarget() before any draw calls you want redirected to the texture. It takes a pointer. 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(sceneTexture, Vector2::Zero, Color::White);
spriteBatch.End();
Restoring the back buffer
Pass nullptr 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 (SetRenderTarget(nullptr)) before the frame ends: draws issued while a non-null render target is bound go to that target, not the screen, so the frame shows nothing. Do not call GraphicsDevice::Present() yourself to finish a frame inside Game::Draw() — Game presents in EndDraw(), and a manual Present() presents twice. Also avoid sampling a render target while it is still bound (EasyGL refuses it).
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 (setTextureProperty takes a Texture2D*)
basicEffect.setTextureProperty(&renderTarget);
basicEffect.setTextureEnabledProperty(true);
One Reach detail to remember when you sample a target with a 3D effect rather than SpriteBatch: the device's default sampler state for slot 0 is LinearWrap, and under the default profile a non-power-of-two texture — which a window-sized render target usually is — may only be sampled with Clamp addressing, otherwise the draw call throws. Set graphicsDevice.getSamplerStatesProperty()[0] = SamplerState::LinearClamp first (SpriteBatch::Begin already uses LinearClamp by default).
Multiple Render Targets (MRT)
MRT is real on 11 identities: the EasyGL identities (OPENGLES3, OPENGL33, WEBGL2), VULKAN (when the device allows more than one colour attachment), WEBGPU, SDL_GPU, DIRECTX9, DIRECTX11, FNA3D, SOFTWARE and METAL (up to 8). It is unavailable on STUB and the 2D-only SDL_RENDERER. It also requires GraphicsProfile::HiDef: under the default Reach profile a set of more than one target throws, and SupportsCapability(GraphicsCapability::MultipleRenderTargets) reports false on every renderer. Pass a collection of RenderTargetBindings (a RenderTarget2D* converts implicitly) to SetRenderTargets(). Each target must have the same dimensions and applied sample count, and the first target owns the depth buffer. Stock effects write attachment 0 only; a custom ShaderEffect fans out to every attachment through layout(location = N) out.
Mixed formats: attachments no longer have to be Color. On renderers that report the format renderable under HiDef (for example SDL_GPU documents “four independently writable, mixed-format colour targets”), a G-buffer can use Rgba1010102 or HalfVector4; check each format with SupportsSurfaceFormatAsRenderTargetEXT(), because an unsupported request quietly becomes Color. Portable G-buffers should still be prepared to encode into RGBA8.
// Ask for HiDef in the Game constructor first (see "Reach and HiDef profile limits").
// Create G-buffer targets (albedo, normals, emissive)
rtAlbedo_ = std::make_unique<RenderTarget2D>(graphicsDevice, 1920, 1080, false,
SurfaceFormat::Color, DepthFormat::Depth24Stencil8, 0,
RenderTargetUsage::DiscardContents);
rtNormals_ = std::make_unique<RenderTarget2D>(graphicsDevice, 1920, 1080, false,
SurfaceFormat::Color, DepthFormat::None, 0,
RenderTargetUsage::DiscardContents);
rtEmissive_ = std::make_unique<RenderTarget2D>(graphicsDevice, 1920, 1080, false,
SurfaceFormat::Color, DepthFormat::None, 0,
RenderTargetUsage::DiscardContents);
// Bind all three at once — geometry pass writes to all simultaneously
graphicsDevice.SetRenderTargets({rtAlbedo_.get(), rtNormals_.get(), rtEmissive_.get()});
graphicsDevice.Clear(Color::Black);
// ... draw geometry here ...
// Restore back buffer for the lighting pass
graphicsDevice.SetRenderTarget(nullptr);
MRT support: HiDef profile required; real on the EasyGL identities, VULKAN, WEBGPU, SDL_GPU, DIRECTX9/11, FNA3D, SOFTWARE and METAL; unavailable on STUB and SDL_RENDERER. Guard the technique with SupportsCapability(GraphicsCapability::MultipleRenderTargets).
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. Cube targets have real storage on every 3D renderer with render targets, and are limited to 512 (power of two) under Reach and 4096 under HiDef. A cube face can be one member of a multi-target set on the EasyGL identities, VULKAN, SOFTWARE, FNA3D and METAL; DIRECTX9/11, SDL_GPU, WEBGPU and HEADLESS refuse it.
// Create a 512x512 cube-map render target (512 is the Reach ceiling)
rtCube_ = std::make_unique<RenderTargetCube>(graphicsDevice, 512,
false, // mipMap
SurfaceFormat::Color,
DepthFormat::Depth24Stencil8,
0,
RenderTargetUsage::DiscardContents);
// Render each face
for (int face = 0; face < 6; ++face) {
graphicsDevice.SetRenderTarget(rtCube_.get(),
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.setEnvironmentMapProperty(rtCube_.get());
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. |
The depth format a target reports (getDepthStencilFormatProperty()) is what the renderer applied, which can differ from what you asked for: Vulkan always creates a depth buffer and reports the format it chose, and SDL_GPU normalises to a supported native format.
RenderTargetUsage enum
| Value | Description |
|---|---|
RenderTargetUsage::DiscardContents |
Default. Contents are considered undefined after SetRenderTarget(nullptr) until the next explicit Clear(); CNA makes them deterministic by clearing on every bind to colour (0, 0, 0, 255), depth 1 and stencil 0. 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. In CNA it preserves contents exactly like PreserveContents, on every renderer (RenderTargetUsagePreservesContentsEXT). |
What happens to the contents on each bind
At this snapshot the shared GraphicsDevice implements RenderTargetUsage the same way on every renderer, through one mapping (RenderTargetUsagePreservesContentsEXT):
DiscardContents: every bind clears the first bound target to a deterministic value, colour(0, 0, 0, 255), depth 1.0 and stencil 0 (each only where that attachment exists). The contents are not left undefined.PreserveContentsandPlatformContents: both preserve. Colour, depth and stencil survive a full unbind and rebind, as in FNA.- An explicit
Clear()after the bind always wins, aspect by aspect. - With several targets bound, the first target's usage decides whether the bind clears.
- The back buffer's
PresentationParameters.RenderTargetUsageis stored but not consulted when you return to the back buffer. - Binding the exact set that is already bound is a no-op: nothing is cleared or resolved, and the viewport is not reset.
Cube targets follow the same rules, with one depth-stencil buffer shared by all six faces. The details and the per-renderer differences in resolve and mip generation are on Render targets: usage, cube faces, resolve and readback.
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. Under HiDef on a renderer that reportsSurfaceFormat::Singlerenderable you can store light-space distance in a single-channel float target; otherwise (and under the default Reach profile) pack depth into RGBA8. - 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 (HiDef) 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) ---
renderTarget_ = std::make_unique<RenderTarget2D>(graphicsDevice, 512, 512);
// --- Per-frame (Draw) ---
// 1. Render scene into the render target
graphicsDevice.SetRenderTarget(renderTarget_.get());
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.getViewportProperty().getWidthProperty(),
graphicsDevice.getViewportProperty().getHeightProperty());
spriteBatch.Begin();
spriteBatch.Draw(*renderTarget_, destRect, Color::White);
spriteBatch.End();
// No Present() here: Game presents in EndDraw().
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. Each blur pass draws the previous target through SpriteBatch with a custom ShaderEffect (the renderer must accept custom effects; see Shader Effects), so the fragment shader samples texture1 (unit 0, where SpriteBatch binds the sprite) and the projection uniform is set by SpriteBatch itself. The shader source is dialect-specific (GLSL ES 3.00 shown in Tutorial 24).
// --- Setup ---
rtScene_ = std::make_unique<RenderTarget2D>(graphicsDevice, 1920, 1080);
rtBlurH_ = std::make_unique<RenderTarget2D>(graphicsDevice, 1920, 1080);
rtBlurV_ = std::make_unique<RenderTarget2D>(graphicsDevice, 1920, 1080);
// Build each effect once and keep it alive; the strings are shader SOURCE, not paths.
blurH_ = std::make_unique<ShaderEffect>(graphicsDevice,
System::IO::File::ReadAllText("Content/shaders/sprite.vert"),
System::IO::File::ReadAllText("Content/shaders/blur_horizontal.frag"));
blurV_ = std::make_unique<ShaderEffect>(graphicsDevice,
System::IO::File::ReadAllText("Content/shaders/sprite.vert"),
System::IO::File::ReadAllText("Content/shaders/blur_vertical.frag"));
// --- Per-frame ---
Rectangle full(0, 0, 1920, 1080);
// Pass 1: render the scene
graphicsDevice.SetRenderTarget(rtScene_.get());
graphicsDevice.Clear(Color::Black);
DrawScene();
// Pass 2: horizontal blur (sample the scene target, write rtBlurH_)
graphicsDevice.SetRenderTarget(rtBlurH_.get());
blurH_->Apply();
blurH_->SetUniformVec2("TexelSize", 1.0f / 1920.0f, 0.0f);
spriteBatch.Begin(SpriteSortMode::Deferred, BlendState::Opaque, nullptr, nullptr, nullptr, blurH_.get());
spriteBatch.Draw(*rtScene_, full, Color::White);
spriteBatch.End();
// Pass 3: vertical blur
graphicsDevice.SetRenderTarget(rtBlurV_.get());
blurV_->Apply();
blurV_->SetUniformVec2("TexelSize", 0.0f, 1.0f / 1080.0f);
spriteBatch.Begin(SpriteSortMode::Deferred, BlendState::Opaque, nullptr, nullptr, nullptr, blurV_.get());
spriteBatch.Draw(*rtBlurH_, full, Color::White);
spriteBatch.End();
// Pass 4: composite to back buffer
graphicsDevice.SetRenderTarget(nullptr);
graphicsDevice.Clear(Color::Black);
spriteBatch.Begin();
spriteBatch.Draw(*rtBlurV_, full, Color::White);
spriteBatch.End();
Code example 3 — MRT g-buffer setup
A minimal deferred rendering geometry pass writing albedo and normals simultaneously. This needs GraphicsProfile::HiDef (see above) and a renderer whose ShaderEffect path fans out to several attachments (EasyGL ES3 generation, WEBGPU; a stock effect writes attachment 0 only).
// --- Setup ---
rtAlbedo_ = std::make_unique<RenderTarget2D>(
graphicsDevice, 1920, 1080, false,
SurfaceFormat::Color, DepthFormat::Depth24Stencil8, 0,
RenderTargetUsage::DiscardContents);
rtNormals_ = std::make_unique<RenderTarget2D>(
graphicsDevice, 1920, 1080, false,
SurfaceFormat::Color, DepthFormat::None, 0, // portable across renderer families
RenderTargetUsage::DiscardContents);
gBufferEffect_ = std::make_unique<ShaderEffect>(graphicsDevice, gbufferVertSrc, gbufferFragSrc);
// --- Geometry pass ---
graphicsDevice.SetRenderTargets({rtAlbedo_.get(), rtNormals_.get()});
graphicsDevice.Clear(Color::Black);
// The fragment shader writes:
// layout(location = 0) out vec4 outAlbedo;
// layout(location = 1) out vec4 outNormal;
gBufferEffect_->getCurrentTechniqueProperty()->getPassesProperty()[0]->Apply();
DrawGeometry();
// --- Lighting pass ---
graphicsDevice.SetRenderTarget(nullptr);
graphicsDevice.Clear(Color::Black);
lightingEffect_->getCurrentTechniqueProperty()->getPassesProperty()[0]->Apply();
lightingEffect_->SetTexture(0, *rtAlbedo_); // RenderTarget2D is-a Texture2D
lightingEffect_->SetTexture(1, *rtNormals_);
lightingEffect_->SetUniformInt("AlbedoMap", 0);
lightingEffect_->SetUniformInt("NormalMap", 1);
DrawFullscreenQuad();
Deep dives on this topic
Long-form pages that explain the exact semantics, invariants and evidence behind this subject.
- DIRECTX11 internals: lifetimes, recovery, readback and target finalisation — DIRECTX11 device creation, the three lifetime groups and device recovery, compute and indirect draws, the HeadlessEXT refusal, back-buffer readback, MRT and cube finalisation and render-target usage.
- GraphicsDevice: the shared device contract — Exact device-level behaviour of CNA's GraphicsDevice: construction and windows, bound state objects, viewport and scissor, Clear and Present, bindings, draw calls, readback and extensions.
- Render targets: usage, cube faces, resolve and readback — RenderTarget2D and RenderTargetCube semantics at the pinned snapshot: construction, binding rules, RenderTargetUsage, cube faces, resolve and mip generation, readback, disposal and lost content.
- Render-target binding, clears, viewport and scissor across renderers — The shared SetRenderTargets transaction, the no-op rebind, what RenderTargetUsage selects, which clears throw, and how each renderer family applies clears, viewport and scissor.
- SDL_GPU uploads, render-target lifetime and swapchain recovery — What SDL_gpu validation exposed in CNA's SDL_GPU renderer, when uploads may cycle, how render targets outlive their wrappers, what MRT writes, and how a failed swapchain acquisition keeps the frame.
- Vulkan draw-time state, ordered clears, occlusion queries and descriptor pools — How CNA's deferred VULKAN renderer carries blend, stencil, viewport and scissor state, orders clears, counts occlusion queries and grows descriptor pools, with the defects behind each rule.
- WebGPU renderer semantics: surfaces, targets, mips and pipeline state — Exact behaviour of CNA's WEBGPU renderer: non-sRGB surface policy, target-relative SpriteBatch coordinates, cube render targets, blit-free mip generation, dynamic and baked state, ordered clears and BC textures.