Recursive AI

Switch from the DXC command line

All oneshader recipes

Replace dxc -spirv with oneshader in a Makefile, CMake or script build: which DXC flags it takes unchanged, what it adds (MSL and WGSL targets, reflection JSON, a dependency file), and which DXC options it rejects.

oneshader, from RecursiveAI.OneShader.Tool, takes DXC’s core flags, so a DXC command line that emits SPIR-V for a vertex, pixel or compute shader mostly works unchanged. It needs the .NET 10 runtime:

Terminal
dotnet tool install -g RecursiveAI.OneShader.Tool --version 1.2.0
oneshader --version

A Makefile, before and after

shaders/post.hlsl includes shaders/common/color.hlsli and uses a macro from the command line:

HLSL
#include "color.hlsli"
Texture2D<float4> Scene : register(t0);
SamplerState Linear : register(s1);
 
float4 main(float2 uv : TEXCOORD0) : SV_Target
{
    return float4(Tonemap(Scene.Sample(Linear, uv).rgb * EXPOSURE), 1);
}

The only change to the Makefile is the compiler’s name, plus --depfile, so that editing the include rebuilds the shader:

MAKE
# Was: DXC = dxc
DXC = oneshader
FLAGS = -spirv -O3 -HV 2021 -WX -I shaders/common -D EXPOSURE=1.5
 
build/post.spv: shaders/post.hlsl
	@mkdir -p build
	$(DXC) $(FLAGS) -T ps_6_0 -E main $< -Fo $@ --depfile build/post.d
 
-include build/post.d

make, then cat build/post.d, then make again:

Text
oneshader -spirv -O3 -HV 2021 -WX -I shaders/common -D EXPOSURE=1.5 -T ps_6_0 -E main shaders/post.hlsl -Fo build/post.spv --depfile build/post.d
build/post.spv: /tmp/oneshader-docs/cookbook/Dxc/shaders/post.hlsl /tmp/oneshader-docs/cookbook/Dxc/shaders/common/color.hlsli
make: 'build/post.spv' is up to date.

After touch shaders/common/color.hlsli, make compiles again. The output passes spirv-val, and DXC compiles the same command line (without --depfile) too.

The flags

From the tool’s documentation, checked against oneshader --help 1.2.0:

DXC flagIn oneshader
-T <profile>vs_6_x, ps_6_x, cs_6_x (also _5_x). The stage comes from the profile; the model number is accepted and otherwise unused.
-E <name>Entry point. With -T and no -E it’s main, as in DXC.
-D NAME[=VALUE], -I <dir>As in DXC.
-Fo <file>Output file. With several entry points or targets, the names are derived from it (name.<entry>.spv, .metal, .wgsl). Without -Fo, a single output goes to standard output.
-spirvSPIR-V, which is also the default. (DXC’s default is DXIL, which oneshader doesn’t write.)
-O0, -Od, -O1..-O3Optimizer off or on (on by default).
-Zpr, -ZpcRow-major or column-major default matrix packing.
-HV 2021Accepted (202x too). Other versions are rejected.
-WXWarnings are errors.
-enable-16bit-typeshalf, min16* and the int16_t family are native 16-bit types, as in DXC (since 1.2.0). See 16-bit and 64-bit types.
-fspv-target-env=vulkan1.2Accepted (vulkan1.3 too). The output is valid for Vulkan 1.2 and newer.
-nologoAccepted and ignored.

What oneshader adds:

OptionMeaning
--target spirv|msl|wgslTarget; repeat for several.
--all-entriesCompile every [shader("vertex"|"pixel"|"compute")] entry point. Also what happens without -T.
--reflect <file.json>Reflection as JSON, deterministic and without machine paths. See Read shader reflection.
--depfile <file>A Make-style dependency file, written on success.
--no-namesLeave source names out of the output.

Diagnostics go to standard error as file(line,col): error OSHnnnn: message, the form IDEs and CMake parse. The exit code is 0 for success, 1 when the shader doesn’t compile, 2 for a wrong command line.

What it rejects

Every other -fvk-* and -fspv-* option, and DXC options such as -Zi or -Fh, stop with a message that names the option (exit code 2):

Text
oneshader: error: option '-Zi' is not supported: debug info is not implemented. Remove it, or use --no-names to drop names from the output. Run 'oneshader --help' for the options.
oneshader: error: option '-fvk-use-dx-layout' is not supported. oneshader always uses DXC's default Vulkan layout and binding rules; use [[vk::binding]], [[vk::location]] and [[vk::offset]] in the shader instead. Run 'oneshader --help' for the options.
oneshader: error: -T gs_6_0 is not a supported profile. Use vs_6_x, ps_6_x or cs_6_x; geometry, tessellation, mesh and library profiles are not supported. Run 'oneshader --help' for the options.
  • Layout and binding remapping (-fvk-b-shift, -fvk-use-dx-layout and the like) isn’t supported. oneshader always applies DXC’s default Vulkan rules, so move the numbers into the shader with [[vk::binding]], [[vk::location]] and [[vk::offset]].
  • DXIL, and geometry, tessellation, mesh, ray-tracing and library profiles, aren’t supported. Keep DXC for those.

Speed

Started once per shader, as in a Makefile, the tool’s speed depends on how it’s built. The repository measures a NativeAOT build at about 2.4 times faster than DXC’s release binary per process, warm. Run as an ordinary .NET process, like the .NET tool installed above, it’s slower than DXC: about 6 times with ReadyToRun and 14 times with the JIT, because starting the runtime dominates a compile that takes 0.34 ms at the median. To get the NativeAOT binary, publish it from the repository with dotnet publish src/OneShader.Tool -c Release -r linux-x64 -o out. The numbers and the method are in Performance against DXC.

How DXC, glslang and OneShader differ on bindings and target environments: HLSL to SPIR-V compiler compared.