Tutorial 150: Build-Time FX Compilation with cna-content
What you’ll learn: Writing an XNA-style .fx effect, compiling it to an Effect .xnb with cna-content (external legacy fxc, optionally under wine), choosing the compiler and processor parameters, reading the failure messages, and loading and drawing the result on a renderer that supports compiled effects.
Read this first — what is and is not verified. CNA never compiles HLSL at run time and does not embed a compiler. .fx source is compiled at build time by cna-content, which drives an external, legacy Microsoft fxc (profile fx_2_0). CNA's own documentation states that this route has not been checked against a genuine Microsoft fxc: its tests substitute the compiler or use a project-owned stand-in. The commands and behaviours below were taken from CNA's source and its build-tool documentation at snapshot 009d40f5; the compile itself needs a compiler you supply, and it was not run against a real fxc while this page was written. If you already have compiled effect bytes (.fxb, or an .xnb from XNA's own content build) skip to the no-compiler route.
XNA samples ship HLSL .fx source. What CNA's Effect(GraphicsDevice&, bytes) constructor and the XNB EffectReader load (Tutorial 128) is the compiled Direct3D 9 Effect Framework container that the XNA content build produced from it. This tutorial closes that gap end to end: write an effect, compile it into an .xnb with cna-content, and draw with it. The whole pipeline is:
Tint.fx --cna-content--> CNA.EffectSourceImporter (resolves #include tree as build dependencies)
CNA.EffectSourceProcessor (profile / defines / debug policy)
external fxc /T fx_2_0 (Windows compiler, or under wine)
CNA.XnbEffectWriter (--format xnb only)
--> Content/Effects/Tint.xnb --ContentManager::Load<std::shared_ptr<Effect>>--> Effect
Prerequisites
- CNA at the
nextbranch (git clone -b next https://github.com/libcna/cna.git, or check out009d40f5dd085c4e674d3479675fac84b12b3e0a) and sharp-runtime on itsnextbranch. Thecna-contentexecutable is thecna_content_toolCMake target and is part of an ordinary CNA build; themain/developbranches are alpha.1 and do not have it. - A renderer that supports compiled effects (Tutorial 128): FNA3D always, or one of the other 13 identities with its
CNA_*_COMPILED_EFFECTSoption on. For example-DCNA_GRAPHICS_RENDERER=OPENGL33 -DCNA_EASYGL_COMPILED_EFFECTS=ON. - An
fxcat profilefx_2_0. It must be Microsoft's legacy compiler from the DirectX SDK (June 2010) — the compiler XNA's own Content Pipeline used. A modern standaloned3dcompiler_47cannot write a legacy effect at all (it fails withE5017: Aborting due to not yet implemented feature: Write pass assignments) becausefxcdelegates thefx_2_0path tod3dx9: the SDK'sd3dx9_43.dllandD3DCompiler_43.dll(from its redistributable cabs) must sit besidefxc.exe. On Linux or macOS you run it throughwine. CNA records the exact identity it used and the extraction commands in modules/renderers/fna3d/effects/README.md; the compiler is neither vendored nor redistributed by CNA. - Windows-style target only. The compiled container is Direct3D 9 bytecode. The default XNB platform is
windows, so you normally pass nothing; the processor refuses the Windows Phone platform (“The Windows Phone platform does not support custom shaders”), and the XNB writer refuses the Xbox 360 platform (big-endian, unverified) unless you pass--xnb-allow-unverified-xbox. There is no Xbox 360, Windows Phone or Metal shader output.
Step 1: write the effect
Create ContentSource/Effects/Tint.fx. It is ordinary XNA-style HLSL Effect Framework source, in the same shape as CNA's own compiler-tested fixture: a matrix, a colour, a texture and sampler, one vertex and one pixel shader at Shader Model 2.0, and one technique.
// ContentSource/Effects/Tint.fx (Shader Model 2.0, so it also fits the Reach profile)
float4x4 WorldViewProjection;
float4 Tint = float4(1, 1, 1, 1);
texture Texture;
sampler TextureSampler = sampler_state
{
Texture = (Texture);
MinFilter = Linear;
MagFilter = Linear;
MipFilter = Linear;
AddressU = Clamp;
AddressV = Clamp;
};
struct VSInput
{
float4 Position : POSITION0;
float2 TexCoord : TEXCOORD0;
};
struct VSOutput
{
float4 Position : POSITION0;
float2 TexCoord : TEXCOORD0;
};
VSOutput MainVS(VSInput input)
{
VSOutput output;
output.Position = mul(input.Position, WorldViewProjection);
output.TexCoord = input.TexCoord;
return output;
}
float4 MainPS(VSOutput input) : COLOR0
{
return tex2D(TextureSampler, input.TexCoord) * Tint;
}
technique Tinted
{
pass P0
{
VertexShader = compile vs_2_0 MainVS();
PixelShader = compile ps_2_0 MainPS();
}
}
Two rules worth knowing. CNA does not rewrite your compile vs_2_0 … statements, so Reach versus HiDef (Shader Model 2 versus 3) is your decision expressed in the source. And the importer records every #include "Common.fxh" in the include tree (up to 256 files, each up to 8 MiB, resolved relative to the including file and confined to the source root) as a build dependency, so editing a header rebuilds only the effects that include it. Also drop a PNG at ContentSource/Textures/checker.png; the same build converts it to a Texture2D XNB.
Step 2: compile it with cna-content
The command line is cna-content build <source> -o <output> --format xnb plus the compiler. Tell it where fxc lives, and on a non-Windows machine which launcher to run it through:
# Linux / macOS: run the Windows compiler through wine
cna-content build ContentSource -o Content --format xnb \
--fx-compiler "$HOME/dxsdk/Utilities/bin/x86/fxc.exe" \
--fx-compiler-launcher wine
# Windows: name the compiler, no launcher
cna-content build ContentSource -o Content --format xnb ^
--fx-compiler "C:\Program Files (x86)\Microsoft DirectX SDK (June 2010)\Utilities\bin\x86\fxc.exe"
A directory build needs an output directory and preserves extensionless relative content names, so the result is:
Content/
├── .cna-content.lock
├── .cna-content-manifest.json
├── Effects/
│ └── Tint.xnb
└── Textures/
└── checker.xnb
A single asset can be built explicitly, in which case the output path's extension must match --format: cna-content build ContentSource/Effects/Tint.fx -o Content/Effects/Tint.xnb --format xnb --fx-compiler …. Add --explain to see why each asset was rebuilt or skipped; an unchanged rebuild is a cheap no-op.
How the compiler is found
The compiler and the launcher are two independent axes, each resolved in its own order; the first one that is set wins:
| Rank | Compiler | Launcher |
|---|---|---|
| 1 | --fx-compiler <path> | --fx-compiler-launcher <program> |
| 2 | CNA_FXC environment variable | CNA_FXC_LAUNCHER environment variable |
| 3 | -DCNA_FXC_EXECUTABLE=<path> at CNA configure time | -DCNA_FXC_LAUNCHER=<program> at configure time |
| 4 | fxc / fxc.exe on PATH | none |
The compiler is probed once per invocation (fxc /?), and the version it reports enters the build fingerprint, so switching compilers rebuilds instead of reusing another compiler's output. Wine is the launcher CNA documents and the only one whose host-to-Windows path translation has been measured. If you build content from CMake and CNA is part of your build, the cna_add_content() function wraps the same tool (TARGET, SOURCE_DIR, OUTPUT_DIR, FORMAT xnb, and so on).
Effect processor parameters
CNA.EffectSourceProcessor accepts exactly three parameters, set per asset in an optional .cna-content.json at the source root (or passed with --config). Unknown names are rejected. Each participates in the build fingerprint.
| Parameter | Type | Meaning |
|---|---|---|
profile | string, reach or hidef | Defines CNA_REACH or CNA_HIDEF for the compile, so one source can branch on it. Defaults to the build's target profile (--xnb-profile, Reach unless given). |
defines | string, NAME=VALUE;NAME2=VALUE2 | Extra preprocessor definitions. Names must be valid identifiers; values may not contain quotes or newlines; a name may not repeat. |
debug | bool | Compile with debug information and no optimisation. Defaults to what --build-configuration says (Release unless you pass Debug), which is what XNA's EffectProcessor.DebugMode = Auto follows. |
{
"format": "CNA.ContentPipeline.Config",
"version": 1,
"assets": {
"Effects/Tint.fx": {
"parameters": {
"profile": { "type": "string", "value": "hidef" },
"defines": { "type": "string", "value": "USE_TINT=1" },
"debug": { "type": "bool", "value": false }
}
}
}
}
When the build fails
- No usable compiler — the first
.fxfails with one complete explanation (“no usable effect compiler: …”) that lists the four ways to name a compiler, the wine launcher, thed3dx9_43.dll/D3DCompiler_43.dllrequirement and the.fxbalternative, rather than failing once per asset. - The compiler rejects the source — the build reports
'Tint.fx': the effect compiler (fxc …) rejected itfollowed by the compiler's own diagnostics, re-emitted with your authored file name and line rather than a temporary path. Warnings are logged, not fatal. - The compiler wrote something that is not an Effect Framework 9.1 container — refused, never embedded. CNA checks the signature of the output before it will call it an XNA
Effect. --format cnb— refused for effects. The CNB container reserves anEffectidentifier but deliberately has no schema for one, because a.cnbholding Direct3D 9 bytecode would be unloadable on every renderer that is not Direct3D 9. Effects are XNB only.- A missing
#include— reported asTint.fx(12): included file 'Common.fxh' does not exist(or, if it escapes the source root, refused).
Step 3: load and draw it
At run time nothing about the compiler matters any more: Tint.xnb is an ordinary XNB Effect asset, read by the EffectReader registered by the XNB built-ins. The renderer must report CompiledEffects; check it, then load by extensionless name.
#include <cstdio>
#include <memory>
#include "Microsoft/Xna/Framework/Game.hpp"
#include "Microsoft/Xna/Framework/GraphicsDeviceManager.hpp"
#include "Microsoft/Xna/Framework/Graphics/Effect.hpp"
#include "Microsoft/Xna/Framework/Graphics/EffectParameter.hpp"
#include "Microsoft/Xna/Framework/Graphics/VertexBuffer.hpp"
#include "Microsoft/Xna/Framework/Graphics/VertexPositionTexture.hpp"
#include "Microsoft/Xna/Framework/Content/ContentLoadException.hpp"
#include "CNA/GraphicsCapability.hpp"
using namespace Microsoft::Xna::Framework;
using namespace Microsoft::Xna::Framework::Graphics;
using Microsoft::Xna::Framework::Content::ContentLoadException;
class TintGame final : public Game {
public:
TintGame() : graphics_(this) {}
protected:
void LoadContent() override {
auto& device = getGraphicsDeviceProperty();
if (!device.SupportsCapability(CNA::GraphicsCapability::CompiledEffects)) {
throw std::runtime_error(
"This renderer build cannot load compiled XNA effects "
"(see Tutorial 128 for the per-renderer build option).");
}
getContentProperty().setRootDirectoryProperty("Content");
try {
tint_ = getContentProperty().Load<std::shared_ptr<Effect>>("Effects/Tint");
} catch (const ContentLoadException& e) {
std::fprintf(stderr, "Could not load Effects/Tint: %s\n", e.what());
throw;
}
checker_ = getContentProperty().Load<Texture2D>("Textures/checker");
// A quad in clip space, so the identity WorldViewProjection is enough.
const VertexPositionTexture quad[6] = {
{ Vector3(-0.5f, 0.5f, 0.0f), Vector2(0.0f, 0.0f) },
{ Vector3( 0.5f, 0.5f, 0.0f), Vector2(1.0f, 0.0f) },
{ Vector3( 0.5f, -0.5f, 0.0f), Vector2(1.0f, 1.0f) },
{ Vector3(-0.5f, 0.5f, 0.0f), Vector2(0.0f, 0.0f) },
{ Vector3( 0.5f, -0.5f, 0.0f), Vector2(1.0f, 1.0f) },
{ Vector3(-0.5f, -0.5f, 0.0f), Vector2(0.0f, 1.0f) },
};
vb_ = std::make_unique<VertexBuffer>(
device, VertexPositionTexture::getVertexDeclarationStatic(), 6, BufferUsage::WriteOnly);
vb_->SetData(quad, 6);
// Parameters come from the compiled effect's reflection; the subscript returns a pointer.
if (auto* p = tint_->getParametersProperty()["WorldViewProjection"])
p->SetValue(Matrix::getIdentityProperty());
if (auto* p = tint_->getParametersProperty()["Tint"])
p->SetValue(Vector4(1.0f, 0.6f, 0.3f, 1.0f));
if (auto* p = tint_->getParametersProperty()["Texture"])
p->SetValue(&checker_); // Texture2D* (a pointer)
}
void Draw(const GameTime&) override {
auto& device = getGraphicsDeviceProperty();
device.Clear(Color::CornflowerBlue);
device.SetVertexBuffer(vb_.get());
for (auto& pass : tint_->getCurrentTechniqueProperty()->getPassesProperty()) {
pass.Apply();
device.DrawPrimitives(PrimitiveType::TriangleList, 0, 2);
}
// No Present(): Game presents in EndDraw().
}
private:
GraphicsDeviceManager graphics_;
std::shared_ptr<Effect> tint_;
Texture2D checker_;
std::unique_ptr<VertexBuffer> vb_;
};
int main() { TintGame game; game.Run(); }
The vertex declaration must supply the inputs the effect's vertex shader declares: POSITION0 and TEXCOORD0 here, which is exactly VertexPositionTexture (a Vector3 position is widened to the shader's float4 with w = 1). You should see a tinted checkerboard quad on cornflower blue. If Load throws, the ContentLoadException wraps the real cause: an ArgumentException for a malformed container, or a NotSupportedException when the renderer lacks CompiledEffects.
The no-compiler route: .fxb and existing XNB
If you already have compiled bytes you need no compiler, and none of the steps above about fxc apply:
- A
.fxbfile (the raw Effect Framework binary, optionally behind XNA 4.0's own wrapper token) is imported byCNA.CompiledEffectImporter, which only checks the container signature:cna-content build ContentSource -o Content --format xnbturns it intoContent/Effects/Name.xnbwith no--fx-compiler. - An
.xnbEffect that XNA's own content build produced can be loaded directly: put it under your content root andLoad<std::shared_ptr<Effect>>it. It is still gated byCompiledEffectson the active renderer. - An XNA
.contentprojcan be built as a whole (cna-content build MyProject.contentproj -o Content): it carries its own platform, profile, compression and per-asset processors, and--fx-compilerreaches a project build too.
What this does not do
- No runtime compilation. Handing
.fxtext, DXBC or MonoGame MGFX toEffect(device, bytes)fails (ArgumentException, orNotSupportedExceptionforMGFX). - No other shader formats. The compiled container is Direct3D 9 bytecode that CNA's MojoShader-based translators (or, on DIRECTX9, the native device) run; there is no Metal, Xbox 360 or Windows Phone effect output.
- Not every renderer. Fourteen of 25 identities run compiled effects; on the others use a stock effect or a source
ShaderEffect(Tutorial 151). - Not proven against genuine
fxcby CNA. If your compile fails on a shader that XNA accepts, treat it as a compatibility report to file rather than a mistake in this tutorial.
Next steps
- Tutorial 128: Load Compiled XNA Effects on Supported Renderers — the runtime side, renderer options and failure taxonomy.
- Tutorial 53: Shader Uniforms and EffectParameter — reflecting techniques, passes, arrays and annotations.
- Effects reference: Compiled XNA effects.
Deep dives on this topic
Long-form pages that explain the exact semantics, invariants and evidence behind this subject.
- Compiled XNA effects: admission, reflection, passes and renderer runtimes — What happens to Direct3D 9 Effect Framework bytecode in CNA: admission order and preflight bounds, the reflected object graph, parameter upload, pass-state publication, cloning, the XNB EffectReader and per-renderer translation.