dotnet/wpf · error · InvalidOperationException
SR.Effect_20ShaderUsing30Registers
Error message
SR.Effect_20ShaderUsing30Registers
What it means
When the PixelShader bytecode changes, ShaderEffect validates that a ps_2_0 shader (major=2, minor=0) is not combined with sampler/register declarations that only exist in ps_3_0 (int and bool registers). Combining them throws InvalidOperationException with Effect_20ShaderUsing30Registers. ps_2_0 hardware/profile has no integer or boolean registers.
Solutions
- Compile the HLSL with /T ps_3_0 so int/bool registers are supported, and set ShaderRenderMode=SoftwareOnly/HardwareOnly as needed.
- Remove int/bool register dependencies (use float registers) if the shader must stay ps_2_0.
- Keep shader profile and the effect's declared constant types consistent (ps_2_0: float-only constants).
Example fix
// before fxc /T ps_2_0 effect.hlsl /Fo effect.ps // effect uses bool registers // after fxc /T ps_3_0 effect.hlsl /Fo effect.ps
Defensive patterns
Strategy: validation
Validate before calling
bool usesPS30Only = usesIntOrBoolRegisters; // int/bool constant DPs
if (pixelShader.ShaderMajorVersion == 2 && pixelShader.ShaderMinorVersion == 0 && usesPS30Only)
throw new InvalidOperationException("ps_2_0 shader cannot use ps_3_0-only registers"); Type guard
static bool IsPS20(PixelShader ps) => ps != null && ps.ShaderMajorVersion == 2 && ps.ShaderMinorVersion == 0; static bool IsPS30(PixelShader ps) => ps != null && ps.ShaderMajorVersion == 3 && ps.ShaderMinorVersion == 0;
Try / catch
try { effect.PixelShader = ps; }
catch (InvalidOperationException ex) when (ex.Message.Contains("ps_2_0") || ex.Message.Contains("registers"))
{
// recompile shader as ps_3_0 or drop int/bool registers
} Prevention
- Match shader profile to declared constant types: int/bool registers require ps_3_0.
- Keep a single source of truth for the shader profile used by an effect.
- When downgrading to ps_2_0, remove all int/bool register dependencies first.
When it happens
Trigger: Setting PixelShaderCompilerUtil/PixelShader property to a ps_2_0 shader while the effect declares register-based dependencies using int or bool constant types (e.g. via PixelShaderConstantCallback on an int/bool property), or switching an existing effect's shader from ps_3_0 back to ps_2_0 without removing int/bool register usage.
Common situations: Downgrading a compiled shader from ps_3_0 to ps_2_0 for wider hardware support while keeping the same C# dependency properties; copying sample code that mixes ps_2_0 bytecode with ps_3_0-only register callbacks.
Understand the failure class
Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.
Related errors
- SR.Effect_ShaderBytecodeSize
- SR.Effect_ShaderConstantType
- SR.Effect_ShaderEffectPadding
- SR.Effect_ShaderPixelShaderSet
- SR.Effect_ShaderSamplerType
AI-assisted analysis of dotnet/wpf@81131a70a4 (2026-09-14).
Data as JSON: /api/errors/fba8722339cce901.
Report an issue: GitHub.
Appendix: source
Thrown at src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Media/Effects/ShaderEffect.cs:186
newShader?._shaderBytecodeChanged += OnPixelShaderBytecodeChanged;
OnPixelShaderBytecodeChanged(PixelShader, null);
}
/// <summary>
/// When the PixelShader's bytecode changes to a ps_2_0 shader, verify that registers only
/// available in ps_3_0 are not being used.
/// </summary>
private void OnPixelShaderBytecodeChanged(object sender, EventArgs e)
{
PixelShader pixelShader = (PixelShader)sender;
if (pixelShader != null &&
pixelShader.ShaderMajorVersion == 2 &&
pixelShader.ShaderMinorVersion == 0 &&
UsesPS30OnlyRegisters())
{
throw new InvalidOperationException(SR.Effect_20ShaderUsing30Registers);
}
}
private bool UsesPS30OnlyRegisters()
{
// int and bool registers are ps_3_0 only
if (_intCount > 0 || _intRegisters != null ||
_boolCount > 0 || _boolRegisters != null)
{
return true;
}
// float registers 32 or above are ps_3_0 only
if (_floatRegisters != null)
{
for (int i = PS_2_0_FLOAT_REGISTER_LIMIT; i < _floatRegisters.Count; i++)
{
if (_floatRegisters[i] != null)View on GitHub (pinned to 81131a70a4)