Tutorial 128: Load Compiled XNA Effects on Supported Renderers

CNA Tutorials  ·  CNA snapshot b0e97bb1

⚠

Format first: this snapshot accepts XNA/FNA D3D9 Effect Framework binary bytecode (commonly stored as .fxb), including the XNA 4 wrapper and an Effect payload inside XNB. It does not compile HLSL .fx source at run time and does not accept DXBC or MonoGame MGFX/.mgfxo. (.fx source can be compiled at build time by cna-content through an external legacy fxc: see Tutorial 150.)

Choose a capable renderer build

Compiled effects work on 13 of the 18 renderer identities, spread over 9 implementation families. FNA3D always supports them; the other eight families need a default-OFF CMake option. A default configure reports GraphicsCapability::CompiledEffects true on FNA3D only.

Renderer familyConfiguration
FNA3DAlways on (no option): FNA3D/MojoShader is part of the renderer
EasyGL identities (OPENGLES2, OPENGLES3, OPENGL33, WEBGL1, WEBGL2)-DCNA_EASYGL_COMPILED_EFFECTS=ON (one option enables all five)
SDL_GPU-DCNA_SDL_GPU_COMPILED_EFFECTS=ON
VULKAN-DCNA_VULKAN_COMPILED_EFFECTS=ON
WEBGPU-DCNA_WEBGPU_COMPILED_EFFECTS=ON (native and Emscripten; the browser build translates the SPIR-V to WGSL)
DIRECTX9, DIRECTX11, DIRECTX12-DCNA_DIRECTX9_COMPILED_EFFECTS=ON, -DCNA_DIRECTX11_COMPILED_EFFECTS=ON, -DCNA_DIRECTX12_COMPILED_EFFECTS=ON (Windows builds)
SOFTWARE-DCNA_SOFTWARE_COMPILED_EFFECTS=ON (a CPU shader executor; needs Python 3 at build time)
Every other family (METAL, HEADLESS, STUB, SDL_RENDERER, CANVAS)CompiledEffects capability is false and cannot be enabled
cmake -S ../cna -B build \
  -DCNA_GRAPHICS_RENDERER=OPENGLES3 \
  -DCNA_EASYGL_COMPILED_EFFECTS=ON

On the GLES and WebGL profiles this snapshot’s pinned MojoShader patches also cover shader-model-3 point-size and fog outputs, shaders that mix absolute and plain constant reads, shader-model-1 colour inputs, volume samplers (OPENGLES3, WEBGL2) and float textures sampled at full precision, and EasyGL emulates the vertex-sampler LOD bias for compiled vertex texture fetches on GLES 3. CNA records 686 of 686 EasyGL compiled-effect tests passing on Mesa’s GLES 3.2 driver; that is CNA’s own record on one driver, not a browser run.

The eight opt-in options default to OFF because they fetch the pinned FNA3D/MojoShader translation machinery (with CNA's managed patches) into a renderer that does not otherwise need it. Enabling an option for one family is not a universal capability promise: an option that is not part of the selected renderer's configure branch has no effect. A renderer name outside the 18 identities is a configure-time error. (Use the CNA next branch at the snapshot this site documents — git clone -b next https://github.com/libcna/cna.git — together with sharp-runtime's next branch; the default branches are alpha.1.)

Construct an Effect from bytes

#include "Microsoft/Xna/Framework/Graphics/Effect.hpp"
#include "CNA/GraphicsCapability.hpp"
#include "System/IO/File.hpp"

using Microsoft::Xna::Framework::Graphics::Effect;
using CNA::GraphicsCapability;

auto& device = getGraphicsDeviceProperty();
if (!device.SupportsCapability(GraphicsCapability::CompiledEffects))
{
    throw std::runtime_error("This renderer build cannot load compiled XNA effects");
}

std::vector<SharpRuntime::bytecs> effectCode =
    System::IO::File::ReadAllBytes("Content/water.fxb");
auto effect = std::make_unique<Effect>(device, effectCode);

// The collections' integer subscripts return pointers (null when out of range).
EffectTechnique* technique = effect->getTechniquesProperty()[0];
effect->setCurrentTechniqueProperty(technique);   // the first technique is already current
for (auto& pass : technique->getPassesProperty())
{
    pass.Apply();
    DrawWaterGeometry();
}

The public constructor validates empty, malformed and oversized input (64 MiB maximum). It reflects techniques, passes, parameters, annotations, arrays and structures; applies pass state; supports independent clones (Effect::Clone()); and can participate in SpriteBatch and ordinary 3D drawing. Your vertex declaration must supply the inputs the effect's vertex shader declares (its POSITION0, TEXCOORD0, … semantics map to VertexElementUsage plus usage index).

ⓘ

Two independent gates. CompiledEffects (this tutorial) and CustomEffects with ExecutesShaderEffectSourceEXT() (source ShaderEffect, Tutorial 52) answer different questions and do not imply one another: FNA3D runs any .fxb yet cannot run a ShaderEffect, while Vulkan and SDL_GPU can run ShaderEffect SPIR-V yet need their option for .fxb. Ask the device for each one you intend to use; GetRendererCapabilityProfileEXT() exposes the finer RendererFeature::CompiledXnaEffects.

Load through ContentManager and EffectReader

#include "Microsoft/Xna/Framework/Graphics/Effect.hpp"

auto effect = getContentProperty().Load<std::shared_ptr<
    Microsoft::Xna::Framework::Graphics::Effect>>(
        "Effects/water"); // resolves the .xnb asset

The XNB built-in registry contains a real EffectReader. It reads an Int32 length (0–64 MiB), extracts the same compiled payload, names the effect after the asset, and creates the same renderer-qualified runtime. An active graphics device is required; an XNB file does not make unsupported renderer builds capable. Every failure — including the “this renderer cannot do it” case — surfaces as a ContentLoadException wrapping the original.

Producing the .xnb (build time)

XNA samples ship .fx source; the XNA content build turns it into the container this tutorial loads. CNA's cna-content tool covers both routes: a .fxb is imported without a compiler, and a .fx is compiled by an external legacy fxc (profile fx_2_0, the DirectX SDK June 2010 build; on Linux or macOS run it through wine) and written as an Effect XNB with --format xnb. CNA embeds no HLSL compiler, and CNA's own documentation notes that the .fx route has not been checked against a genuine Microsoft fxc.

cna-content build Content -o build/Content --format xnb \
    --fx-compiler /opt/dxsdk/fxc.exe --fx-compiler-launcher wine

The walkthrough, with the compiler discovery order and the failure messages, is Tutorial 150: Build-Time FX Compilation with cna-content.

Set parameters and apply passes

Use the reflected parameter, technique and pass collections as in XNA, spelled the CNA way. Parameters are looked up by name and return a pointer (null when absent); values are stored in the compiled runtime and uploaded when a pass is applied and the parameter is dirty; clone state is independent.

EffectParameter* world = effect->getParametersProperty()["World"];
if (world != nullptr)
    world->SetValue(worldMatrix);                 // Matrix

if (auto* texture = effect->getParametersProperty()["WaterTexture"])
    texture->SetValue(&waterTexture);             // Texture2D* (a pointer)

effect->getCurrentTechniqueProperty()->getPassesProperty()[0]->Apply();
graphicsDevice.DrawIndexedPrimitives(PrimitiveType::TriangleList, 0, 0, vertexCount, 0, indexCount / 3);

This is a separate path from CNAEXT ShaderEffect, whose uniforms are set through SetUniformXxx() and whose input is renderer-native source. See Tutorial 53 for the full EffectParameter surface.

Interpret failures precisely

Malformed input is judged before the renderer is consulted, so the same bytes fail the same way everywhere; only a valid binary on an incapable renderer reaches the capability check.

  • System::ArgumentException: empty, oversized (over 64 MiB) or structurally invalid input — including DXBC, GLSL or SPIR-V handed over as if it were an Effect Framework payload.
  • System::NotSupportedException: the active renderer lacks CompiledEffects; or the input is a recognised unsupported container (its first four bytes are MGFX); or the renderer advertised the capability but failed to create the runtime.
  • std::runtime_error: a shader-translation or render-state failure inside the runtime, for example an unsupported render state, or Border or MirrorOnce sampler addressing (not representable on some renderers).
  • ContentLoadException: any of the above while loading through XNB EffectReader, plus an invalid declared bytecode length.
  • A source file named .fx is not compiled at run time. Compile it with cna-content (Tutorial 150) or an XNA/FNA-compatible Effect Framework toolchain before packaging.

Next steps