Graphics State
Implementation status: All four state-object families are fully implemented. Built-in presets and custom state creation work on both the EasyGL and Vulkan backends. See per-section notes for backend-specific limitations.
Overview
All render state in CNA follows the XNA 4.0 immutable state-object pattern. Rather than toggling individual render states through a stateful device API, you create a state object (either a built-in preset or a custom instance), then assign it to the appropriate property on GraphicsDevice before issuing draw calls.
The four state-object types and where they live on GraphicsDevice:
graphicsDevice.BlendState— controls how incoming fragment colours are blended with the render targetgraphicsDevice.DepthStencilState— controls depth testing, depth writing, and stencil operationsgraphicsDevice.RasterizerState— controls face culling, fill mode, and scissor/depth biasgraphicsDevice.SamplerStates[n]— per-slot texture filtering and address modes (up to 16 slots)
State assigned to GraphicsDevice applies to every draw call that follows until you change it again. A common mistake is forgetting to reset state between rendering passes — for example, leaving BlendState::Additive active when starting the opaque geometry pass. Always set state explicitly at the beginning of each logical pass.
Always reset state after each pass. State is persistent — it does not reset automatically between frames or between draw calls. Set the appropriate preset at the start of every opaque pass, every transparent pass, and every 2D/HUD pass.
BlendState
BlendState describes how the output of a pixel shader (the source) is combined with the existing colour already in the render target (the destination). CNA provides four built-in presets that cover the most common use cases.
Built-in presets
| Preset | Use case |
|---|---|
BlendState::Opaque |
Fully opaque geometry — blending is disabled entirely. The fastest blend mode; use it for all solid meshes. |
BlendState::AlphaBlend |
Standard transparency using premultiplied alpha. This is the default for SpriteBatch and the correct choice for textures exported from tools that premultiply alpha at export time. |
BlendState::NonPremultiplied |
Straight (non-premultiplied) alpha. Use when loading PNG files whose RGB channels have not been premultiplied by the alpha value — a common default for art tools that export plain PNG. |
BlendState::Additive |
Additive (light-accumulating) blending. Source colour is added directly to the destination without obscuring it. Ideal for particle effects, glow, fire, and laser beams. |
Setting BlendState
// Use a built-in preset
graphicsDevice.BlendState = BlendState::AlphaBlend;
// Draw transparent geometry...
// Reset to opaque for the next pass
graphicsDevice.BlendState = BlendState::Opaque;
Custom BlendState
When the presets are not sufficient, create a BlendState and configure its fields directly:
| Field | Type | Description |
|---|---|---|
ColorBlendFunction | BlendFunction | Equation used to combine source and destination colour channels |
ColorSourceBlend | Blend | Source colour multiplier |
ColorDestinationBlend | Blend | Destination colour multiplier |
AlphaBlendFunction | BlendFunction | Equation used to combine source and destination alpha channels |
AlphaSourceBlend | Blend | Source alpha multiplier |
AlphaDestinationBlend | Blend | Destination alpha multiplier |
BlendFactor | Color | Constant colour used when Blend::BlendFactor is selected |
ColorWriteChannels | ColorWriteChannels | Bitmask of RGBA channels that are written to the render target |
// Custom multiply blend
BlendState multiply;
multiply.ColorBlendFunction = BlendFunction::Add;
multiply.ColorSourceBlend = Blend::DestinationColor;
multiply.ColorDestinationBlend = Blend::Zero;
multiply.AlphaBlendFunction = BlendFunction::Add;
multiply.AlphaSourceBlend = Blend::One;
multiply.AlphaDestinationBlend = Blend::Zero;
graphicsDevice.BlendState = multiply;
DepthStencilState
DepthStencilState controls whether incoming fragments are tested against the depth buffer and whether they write their depth value back. It also governs stencil operations for masking and outlining effects.
Built-in presets
| Preset | Use case |
|---|---|
DepthStencilState::Default |
Depth test enabled, depth write enabled. The correct setting for opaque 3D geometry — closer geometry overwrites farther geometry. |
DepthStencilState::DepthRead |
Depth test enabled, depth write disabled. Used for transparent 3D objects: they are occluded by opaque geometry but do not themselves occlude each other or update the depth buffer. |
DepthStencilState::None |
No depth test and no depth write. The correct setting for 2D UI and SpriteBatch rendering where painter’s order (draw order) determines visibility. |
Setting DepthStencilState
// Standard 3D opaque pass
graphicsDevice.DepthStencilState = DepthStencilState::Default;
// Transparent 3D pass (test but do not write depth)
graphicsDevice.DepthStencilState = DepthStencilState::DepthRead;
// 2D HUD / SpriteBatch pass
graphicsDevice.DepthStencilState = DepthStencilState::None;
Custom DepthStencilState
| Field | Type | Description |
|---|---|---|
DepthBufferEnable | bool | Enables the depth test |
DepthBufferWriteEnable | bool | Enables writing the depth value to the depth buffer on pass |
DepthBufferFunction | CompareFunction | Comparison used for the depth test (default: Less) |
StencilEnable | bool | Enables stencil testing |
StencilFunction | CompareFunction | Comparison used for the stencil test |
StencilPass | StencilOperation | Operation applied when both stencil and depth tests pass |
StencilFail | StencilOperation | Operation applied when the stencil test fails |
ReferenceStencil | int | Reference value compared against the stencil buffer |
RasterizerState
RasterizerState controls how triangles are converted to fragments. The most commonly adjusted settings are face culling and fill mode.
Built-in presets
| Preset | Use case |
|---|---|
RasterizerState::CullCounterClockwise |
Default. Back faces (counter-clockwise winding in screen space) are culled. Use this for all standard 3D geometry with correctly wound vertices. |
RasterizerState::CullClockwise |
Cull back faces where clockwise winding defines the front face. Use when importing geometry from tools that use a different handedness convention. |
RasterizerState::CullNone |
No face culling — both sides of every triangle are rasterised. Use for flat double-sided geometry such as leaves, cloth, or decals. |
Setting RasterizerState
// Default for 3D (already the default on GraphicsDevice)
graphicsDevice.RasterizerState = RasterizerState::CullCounterClockwise;
// Double-sided geometry
graphicsDevice.RasterizerState = RasterizerState::CullNone;
Custom RasterizerState
| Field | Type | Description |
|---|---|---|
CullMode | CullMode | Which faces are culled: None, CullClockwiseFace, or CullCounterClockwiseFace |
FillMode | FillMode | Solid (default) or WireFrame — see note below |
MultiSampleAntiAlias | bool | Enables MSAA rasterisation when the render target is multisampled |
ScissorTestEnable | bool | Clips rasterisation to graphicsDevice.ScissorRectangle |
DepthBias | float | Constant depth offset added to each fragment — used to prevent z-fighting on coplanar surfaces |
SlopeScaleDepthBias | float | Depth bias scaled by the polygon slope — useful for shadow mapping |
WireFrame backend limitation: FillMode::WireFrame requires the Vulkan backend. It is not available on the EasyGL backend and will be silently ignored if set. Use it only when you can guarantee a Vulkan render path.
SamplerState
SamplerState describes how textures are sampled — the filtering algorithm applied when a texture is magnified or minified, and how texture coordinates outside the [0, 1] range are handled. Up to 16 independent sampler slots are available, addressed via graphicsDevice.SamplerStates[n].
Built-in presets
| Preset | Use case |
|---|---|
SamplerState::LinearClamp |
Bilinear filtering with clamping at texture edges. The most common preset for UI textures, skyboxes, and any texture that should not tile. |
SamplerState::LinearWrap |
Bilinear filtering with tiling/repeating. Use for repeating terrain textures, tiled floors, and other tileable surfaces. |
SamplerState::PointClamp |
Nearest-neighbour (point) filtering with clamping. Essential for pixel art, retro-style games, and any content where bilinear blurring is undesirable. |
SamplerState::PointWrap |
Nearest-neighbour filtering with tiling. Use for pixel-art tilesets and sprite sheets that need to repeat. |
SamplerState::AnisotropicClamp |
Anisotropic filtering with clamping. Preserves texture sharpness on surfaces viewed at a glancing angle (floors, roads). Higher quality than bilinear at the cost of a small GPU overhead. |
SamplerState::AnisotropicWrap |
Anisotropic filtering with tiling. Best quality for repeating terrain and surface textures seen at oblique angles. |
Setting SamplerState
// Set slot 0 for the primary texture
graphicsDevice.SamplerStates[0] = SamplerState::LinearClamp;
// Set slot 1 for a secondary (e.g. lightmap) texture
graphicsDevice.SamplerStates[1] = SamplerState::LinearWrap;
Custom SamplerState
| Field | Type | Description |
|---|---|---|
Filter | TextureFilter | Filtering algorithm: Linear, Point, Anisotropic, and mip-level variants |
AddressU | TextureAddressMode | Behaviour outside [0,1] on the U axis: Wrap, Clamp, Mirror, or Border |
AddressV | TextureAddressMode | Behaviour outside [0,1] on the V axis |
AddressW | TextureAddressMode | Behaviour outside [0,1] on the W axis (volume textures) |
MaxAnisotropy | int | Maximum anisotropy level (1–16) used when Filter is Anisotropic |
MipMapLevelOfDetailBias | float | Biases mip-map selection; negative values select sharper (higher-resolution) mips |
MaxMipLevel | int | Clamps mip-map selection to this level or coarser (0 = no limit) |
Viewport and ScissorRectangle
Two additional device properties control which region of the render target receives output.
Viewport
graphicsDevice.Viewport is a Viewport struct that defines the sub-rectangle of the render target used for rendering and the depth range:
// Full-screen viewport with default depth range
graphicsDevice.Viewport = Viewport(0, 0, screenWidth, screenHeight, 0.0f, 1.0f);
// Split-screen: render to the left half
graphicsDevice.Viewport = Viewport(0, 0, screenWidth / 2, screenHeight, 0.0f, 1.0f);
The constructor parameters are (x, y, width, height, minDepth, maxDepth). minDepth and maxDepth default to 0.0f and 1.0f and should rarely need to change.
ScissorRectangle
graphicsDevice.ScissorRectangle is a Rectangle that clips rasterisation to a sub-region. It only takes effect when RasterizerState.ScissorTestEnable is true:
// Enable scissor test in the rasterizer state
RasterizerState scissorRS;
scissorRS.CullMode = CullMode::CullCounterClockwiseFace;
scissorRS.ScissorTestEnable = true;
graphicsDevice.RasterizerState = scissorRS;
// Define the scissor region
graphicsDevice.ScissorRectangle = Rectangle(100, 100, 400, 300);
// Only pixels inside (100,100)-(500,400) are written
graphicsDevice.DrawPrimitives(PrimitiveType::TriangleList, 0, triangleCount);
// Disable scissor test afterwards
graphicsDevice.RasterizerState = RasterizerState::CullCounterClockwise;
Common patterns
Pattern 1: 3D scene with 2D HUD
The most common pattern in any game: render the 3D world, then overlay a 2D HUD using SpriteBatch. State must be switched explicitly between the two passes.
// --- 3D world pass ---
graphicsDevice.BlendState = BlendState::Opaque;
graphicsDevice.DepthStencilState = DepthStencilState::Default;
graphicsDevice.RasterizerState = RasterizerState::CullCounterClockwise;
graphicsDevice.SamplerStates[0] = SamplerState::LinearWrap;
DrawScene(graphicsDevice);
// --- 2D HUD pass ---
// SpriteBatch sets its own blend and depth state internally,
// but setting them explicitly before Begin() is good practice.
graphicsDevice.BlendState = BlendState::AlphaBlend;
graphicsDevice.DepthStencilState = DepthStencilState::None;
graphicsDevice.RasterizerState = RasterizerState::CullNone;
graphicsDevice.SamplerStates[0] = SamplerState::LinearClamp;
spriteBatch.Begin();
DrawHUD(spriteBatch);
spriteBatch.End();
Pattern 2: Opaque then transparent 3D objects
Opaque objects should always be drawn before transparent objects. After the opaque pass, switch to DepthRead and sort transparent objects back-to-front before drawing.
// --- Opaque pass ---
graphicsDevice.BlendState = BlendState::Opaque;
graphicsDevice.DepthStencilState = DepthStencilState::Default;
for (auto& mesh : opaqueMeshes)
mesh.Draw(graphicsDevice, effect);
// --- Transparent pass ---
// Sort transparent objects back-to-front from the camera
std::sort(transparentMeshes.begin(), transparentMeshes.end(),
[&](const auto& a, const auto& b) {
return a.DistanceTo(camera) > b.DistanceTo(camera);
});
graphicsDevice.BlendState = BlendState::AlphaBlend;
graphicsDevice.DepthStencilState = DepthStencilState::DepthRead;
for (auto& mesh : transparentMeshes)
mesh.Draw(graphicsDevice, effect);
Pattern 3: Additive particle system
Particle effects such as fire, sparks, and laser bolts accumulate light additively. Depth writing must be disabled so particles do not occlude each other based on draw order.
graphicsDevice.BlendState = BlendState::Additive;
graphicsDevice.DepthStencilState = DepthStencilState::DepthRead;
graphicsDevice.RasterizerState = RasterizerState::CullNone;
graphicsDevice.SamplerStates[0] = SamplerState::LinearClamp;
particleSystem.Draw(graphicsDevice, camera);
// Reset state for subsequent passes
graphicsDevice.BlendState = BlendState::Opaque;
graphicsDevice.DepthStencilState = DepthStencilState::Default;
Code examples
Example 1: Setting BlendState for HUD sprites
// Non-premultiplied PNG loaded from disk (straight alpha)
graphicsDevice.BlendState = BlendState::NonPremultiplied;
spriteBatch.Begin(SpriteSortMode::Deferred,
BlendState::NonPremultiplied);
spriteBatch.Draw(hudTexture, Vector2(10.0f, 10.0f), Color::White);
spriteBatch.End();
// Return to premultiplied alpha for the next SpriteBatch call
graphicsDevice.BlendState = BlendState::AlphaBlend;
Example 2: 3D opaque and transparent pass setup
void Game::Draw(GameTime gameTime)
{
graphicsDevice.Clear(Color::CornflowerBlue);
// Opaque geometry
graphicsDevice.BlendState = BlendState::Opaque;
graphicsDevice.DepthStencilState = DepthStencilState::Default;
graphicsDevice.RasterizerState = RasterizerState::CullCounterClockwise;
DrawOpaqueMeshes();
// Transparent geometry (test depth, do not write)
graphicsDevice.BlendState = BlendState::AlphaBlend;
graphicsDevice.DepthStencilState = DepthStencilState::DepthRead;
DrawTransparentMeshes();
// 2D overlay
graphicsDevice.DepthStencilState = DepthStencilState::None;
spriteBatch.Begin();
DrawHUD();
spriteBatch.End();
}
Example 3: Additive particle blending
// Particle system update + draw
void ParticleSystem::Draw(GraphicsDevice& gd, const Camera& cam)
{
gd.BlendState = BlendState::Additive;
gd.DepthStencilState = DepthStencilState::DepthRead;
gd.RasterizerState = RasterizerState::CullNone;
gd.SamplerStates[0] = SamplerState::LinearClamp;
particleEffect->SetView(cam.View());
particleEffect->SetProjection(cam.Projection());
for (auto& batch : particleBatches) {
particleEffect->SetWorld(batch.Transform);
particleEffect->SetTexture(batch.Texture);
particleEffect->CurrentTechnique->Passes[0]->Apply();
gd.DrawPrimitives(PrimitiveType::TriangleList,
batch.VertexOffset, batch.PrimitiveCount);
}
}
Example 4: Custom RasterizerState (no culling + wireframe on Vulkan)
// Build a custom rasterizer state for debug wireframe rendering.
// WireFrame fill mode requires the Vulkan backend.
RasterizerState wireframeState;
wireframeState.CullMode = CullMode::None;
wireframeState.FillMode = FillMode::WireFrame; // Vulkan only
wireframeState.MultiSampleAntiAlias = false;
if (graphicsDevice.IsVulkanBackend()) {
graphicsDevice.RasterizerState = wireframeState;
DrawSceneWireframe();
graphicsDevice.RasterizerState = RasterizerState::CullCounterClockwise;
}
Example 5: Per-slot sampler state for multi-texture
// DualTextureEffect uses two texture slots.
// Slot 0: diffuse texture with linear wrap (tiling surface)
// Slot 1: lightmap with linear clamp (unique per-object UV)
graphicsDevice.SamplerStates[0] = SamplerState::LinearWrap;
graphicsDevice.SamplerStates[1] = SamplerState::LinearClamp;
auto effect = std::make_shared<DualTextureEffect>(graphicsDevice);
effect->SetWorld(Matrix::Identity);
effect->SetView(camera.View());
effect->SetProjection(camera.Projection());
effect->SetTexture(diffuseTexture);
effect->SetTexture2(lightmapTexture);
effect->CurrentTechnique->Passes[0]->Apply();
graphicsDevice.DrawIndexedPrimitives(
PrimitiveType::TriangleList, 0, 0, indexCount / 3);