Recursive AI

Changelog

All oneshader docs

All notable changes to the packages RecursiveAI.OneShader, RecursiveAI.OneShader.Build and RecursiveAI.OneShader.Tool. Versions follow Semantic Versioning.

[Unreleased]

1.2.0

  • 16-bit and 64-bit types. With CompileOptions.Enable16BitTypes (DXC's -enable-16bit-types; MSBuild OneShaderEnable16BitTypes, item metadata Enable16BitTypes, CLI -enable-16bit-types) half, min16float, min10float, min16int, min16uint and float16_t/int16_t/uint16_t (vectors and matrices) are native 16-bit types, in arithmetic and in buffers with DXC's byte layout; without it the 16-bit names other than half/min* are error OSH0360, and the min-precision types are 32-bit values marked RelaxedPrecision on SPIR-V, as DXC does. double, int64_t and uint64_t work on SPIR-V in arithmetic, buffers, asdouble, asuint(double, lo, hi) and 64-bit Interlocked* on structured buffers and group-shared memory. A double argument to a float-only intrinsic (floor, sqrt, ...) is narrowed to float as DXC does, with the new warning OSH0361. Unsuffixed float literals keep double precision until they meet a concrete type. Constant folding is exact at each width.
  • MSL: native half, short, ushort, long and ulong; double, 64-bit integer vectors in buffers and 64-bit atomics other than unsigned InterlockedMax/InterlockedMin without a used result (the only form Metal 2.4 has) are errors at the first use (OSH5102) that say what to change. WGSL: half is f16 with enable f16; (16-bit values in buffers are read and written as halves of 32-bit words; stores use atomic and/or on the word so neighbours written by other invocations are not disturbed); 16-bit integers, double and 64-bit integers are errors at the first use (OSH5202). A refusal is an error only when that target is selected.
  • Reflection: EntryPointInfo.RequiredFeatures (DeviceFeatures: Float16, Int16, Float64, Int64, StorageBuffer16, UniformBuffer16, PushConstant16, InputOutput16, Int64Atomics; the serialized entry record gets an optional features child). EntryLayout.RequiredFeatures and PipelineLayout.RequiredFeatures; PipelineLayoutInfo.RequiredFeatures (the Vulkan device features to enable); WebGpuPipelineLayout.RequiredFeatures lists shader-f16. Generated entry classes get a RequiredFeatures constant beside SubgroupOperations, and the generated Layout carries it.
  • Default vertex formats for 16-bit inputs: Float16x2/Float16x4, Sint16x2/Sint16x4, Uint16x2/Uint16x4.
  • Pipeline layout helpers (OneShader.Layouts), additions only. PipelineLayout.TryCreateUnion builds one layout for any number of entry points, several per stage; resources at one slot that agree merge with united stages, disagreements are a LayoutError naming both entry points and the slot, and push-constant blocks merge into one covering range when their overlapping members agree (EntryLayout.PushConstantType, WithPushConstantType). Entry points compiled from different files that disagree on a Metal index or WebGPU push slot are reported by TryToMetal/TryToWebGpu. VulkanLayoutOptions and WebGpuLayoutOptions gain stage widening (WidenSets/WidenGroups, WidenBindings, WidenPushConstants) and dynamic offsets by slot (DynamicBindings; WebGPU also DynamicBuffers); ResourceLayout.DynamicOffset, DescriptorSetLayoutBindingInfo.IsDynamic and BindGroupLayoutEntryInfo.HasDynamicOffset expose the flag. EntryLayout.TryWithVertexOverrides / VertexLayout.TryWithOverrides apply the host's stride, and per-location format and offset, checked against the shader's input type (LayoutErrorKind.InvalidVertexInput); Vulkan, Metal and WebGPU vertex descriptions follow. TryToMetal also reports two resources of one stage that take the same Metal index.
  • Specialization constants. [[vk::constant_id(N)]] const <bool|int|uint|float> Name = <default>; becomes each target's pipeline-time constant, so one compiled shader serves many variants without recompiling. SPIR-V: OpSpecConstant/OpSpecConstantTrue/False with SpecId N, the ids DXC writes for the same source. MSL: constant T Name [[function_constant(N)]], read through is_function_constant_defined with the default as the fallback. WGSL: @id(N) override Name: T = default;. Optimization never folds a specialization constant to its default; code derived from one (including static const initializers) is ordinary code that runs per pipeline. Mistakes are errors at the HLSL line: a duplicate id, a vector or struct type, static, a missing or non-constant default, a negative id (OSH0335); an array size, [numthreads] argument or case label that uses one says why it is refused (OSH0310), as DXC refuses the same uses.
  • Reflection: ShaderReflection.SpecConstants (SpecConstantInfo: name, id, scalar type, default bits and value, a SpecConstantSlot per target with the SPIR-V SpecId, the Metal function-constant index and variable, the WGSL @id and override name) and EntryPointInfo.UsedSpecConstants. The serialized form gets spec and specuse records, written only for shaders that have constants; the command-line tool's JSON gets specConstants and usedSpecConstants likewise. InterfaceCompatibility reports a removed constant or a changed id or type (InterfaceDifferenceKind.SpecConstant); new constants and changed defaults stay compatible.
  • OneShader.Layouts: SpecializationValues (from EntryLayout.SpecConstants; typed Set, plain data) converts to Vulkan VkSpecializationInfo contents (Layouts.Vulkan.SpecializationInfo: map entries and the data block), Metal function-constant values (Layouts.Metal.FunctionConstantValue: index, MTLDataType number, bits) and the WebGPU constants record keyed by decimal id. EntryLayout gets SpecConstants.
  • Generated code: SpecializationConstants.<Name>.Id/Default constants, a blittable Specialization struct (one 4-byte field per constant, Defaults, ToVulkan/ToMetal/ToWebGpu) and SpecConstantIds on every entry-point class that reads constants; the entry's Layout carries them. In a variant file each variant class has its own SpecializationConstants and Specialization (a constant may exist in only some variants), and the aggregate entry class has SpecConstantIds for the variant it was looked up in.
  • CompileOptions.SpecializationOverrides fixes chosen constants at compile time: they become plain constants, get folded, and leave reflection. A value that does not fit the type is error OSH0335; an unknown name is warning OSH0335.
  • Metal note: a function that declares function constants cannot build a pipeline state as it is, even when every default is wanted; create it with makeFunction(name:constantValues:) (an empty MTLFunctionConstantValues selects the defaults).
  • Shader variants in the build package. A OneShader item takes Axes="ALPHA_TEST;LIGHTING=NONE|LAMBERT|PBR" (flags and choices; the cartesian product is compiled), or OneShaderVariant items name hand-picked define sets. One compile per variant per selected target, in parallel; only variants whose define set, shader, includes or options changed are recompiled. More than 64 variants is error OSH0905 (raise it with MaxVariants / OneShaderMaxVariants); malformed declarations are OSH0904 and OSH0906. An error that happens under some variants is reported once on the HLSL line with the define set ((variant ALPHA_TEST=1, LIGHTING=PBR)).
  • Generated code for a file with variants: Key (one property per axis, enums for choices), Variants.Get(key) returning a Variant with an Entry per entry point (SpirV, Msl, Wgsl, Layout, ...), Bindings and Pipeline per variant, and a static class per variant shaped like a single file's class. Constant-buffer, push-constant and vertex structs are generated once when all variants agree on the layout, per variant when they differ. Files without variants generate exactly the same C# as before. See docs/generated-bindings.md.
  • A file listed explicitly as a OneShader item (to give it metadata) is no longer added a second time by the default **/*.hlsl glob.
  • ShaderCompileCache (RecursiveAI.OneShader): thread-safe, bounded compile cache keyed by source, path, options and the content of the includes the compile read; concurrent calls for one key compile once; Clear() empties it.
  • The build task logs OneShader: N compiled, M up to date. at low importance (-v:d).

1.1.0

  • New package RecursiveAI.OneShader.Tool: the oneshader command-line compiler (a .NET tool, and a NativeAOT single-file binary with dotnet publish -r <rid>). It takes DXC's core flags (-T, -E, -D, -I, -Fo, -spirv, -O0..-O3, -Zpr/-Zpc, -HV 2021, -WX) plus --target spirv|msl|wgsl (repeatable), --all-entries, --reflect <file.json> and --depfile <file>. Diagnostics print as file(line,col): error OSHnnnn: message; exit code 0, 1 for compile errors, 2 for command-line errors.
  • Pipeline layout helpers (OneShader.Layouts): plain data, no graphics API behind it, that turns a shader into what a renderer creates. EntryLayout (from reflection with EntryLayout.TryCreate, or the generated Sprite.VSMain.Layout) and PipelineLayout.TryCreate merge entry points into bind groups with combined stage visibility and push-constant blocks, returning conflicts as a LayoutError value. ToVulkan() gives descriptor set layouts, push-constant ranges and vertex input with numeric VkDescriptorType/VkShaderStageFlagBits/VkFormat values; TryToMetal() gives per-stage buffer, texture and sampler indices, the setBytes slot and MTLVertexDescriptor data; TryToWebGpu() gives bind group layout entries with the WebGPU enum strings, push constants as a uniform buffer at the reserved group and binding, and vertex buffer layouts. Generated classes gain Layout on every entry point and Pipeline (vertex + pixel, or one compute entry point) on the file.
  • Wave (subgroup) and quad intrinsics on all three targets: WaveGetLaneCount/Index, WaveIsFirstLane, WaveActiveAnyTrue/AllTrue/AllEqual/Ballot/Sum/Product/Min/Max/BitAnd/BitOr/BitXor/CountBits, WavePrefixSum/Product/CountBits, WaveReadLaneAt/First and QuadReadAcrossX/Y/Diagonal/QuadReadLaneAt on 32-bit int, uint and float scalars and vectors. SPIR-V uses GroupNonUniform* as DXC does, MSL simd_*/quad_*, WGSL the subgroups feature (enable subgroups;; not in vertex shaders). WaveMatch and WaveMultiPrefix* are an error (OSH0350). Reflection gains EntryPointInfo.SubgroupOperations (Vulkan's VkSubgroupFeatureFlagBits values), EntryPointInfo.UsesSubgroups and ShaderReflection.UsesSubgroups; EntryLayout/PipelineLayout/PipelineLayoutInfo/MetalPipelineLayout carry SubgroupOperations, WebGpuPipelineLayout.RequiredFeatures lists subgroups, and generated entry classes get a SubgroupOperations constant. BuiltIn gains SubgroupLocalInvocationId and SubgroupSize.
  • Reflection: ResourceInfo.ImageFormat records the [[vk::image_format]] name of a storage texture (the serialized resource record gets an optional format attribute, written only when set).

1.0.0

First stable release. The public API (ShaderCompiler, CompileOptions, CompileResult, reflection, InterfaceCompatibility) and the generated C# shape are now covered by Semantic Versioning.

  • Vertex, pixel and compute shaders in modern HLSL (HLSL 2021 templates included) compile to optimized SPIR-V for Vulkan 1.2, Metal Shading Language and WGSL, each with reflection; the build package compiles *.hlsl during dotnet build, reports errors on the HLSL line, embeds the selected targets (OneShaderTargets) and generates C# structs and binding code.
  • Since 0.4: the packages are built with deterministic paths, embedded untracked sources and a symbol package (.snupkg); package metadata links to the public repository; the four-platform CI (Windows x64, Linux x64 and arm64, macOS arm64) runs the compiler tests, the build-package tests and the NativeAOT smoke app, and runs the reference-tool tests (DXC, spirv-val, naga) wherever those tools are released.
  • Tested: the five fuzz targets (preprocessor, front end, IR and optimizer, every backend) ran clean for 600 s each, and the corpus is checked with spirv-val, by running it on GPUs against DXC's output, with naga, with Apple's metal compiler and on WebGPU.

0.4.0-preview.1

  • Hardening from fuzzing every compile path: crashes, hangs and invalid SPIR-V or WGSL on unusual input are now diagnostics. Errors are capped at 100 per compile (OSH0004) and identical consecutive errors are dropped; array dimensions are limited to 16, local array initializers to 4096 elements, and binding, set and register numbers to 65535; the preprocessor's default output and expansion limits are 2M tokens and 20M steps. Component-wise intrinsics on matrices (saturate, lerp, pow, sin and others) work on every target.
  • Very deep call chains no longer overflow the stack on threads with a small stack (seen on macOS).
  • WGSL: WgslBackend.EntryFunctionName gives the name an entry point has in the WGSL text (reserved words such as loop or fragment get a trailing underscore). The build task uses it for WgslEntryPoint instead of reading the generated text, and a test checks entry points named after reserved words in MSL and WGSL against naga and metal.
  • MSL: switch cases that fall through and a default in the middle now run correctly. Before, a case entered by falling through re-ran the switch edge's phi copies and overwrote the value it fell in with, and a break or continue inside an if inside a case was dropped. WGSL, which has no fall-through, repeats the following case's statements; both are covered by the new lang_switch_flow parity case.
  • MSL honours precise under Metal's default fast math: functions with precise arithmetic turn off reassociate and contract, and precise divisions use precise::divide.
  • precise on local variables, assignments and function return values now marks the arithmetic that feeds them in every target (the SPIR-V output gets NoContraction on the same operations as DXC; before, the modifier was parsed and ignored).
  • WGSL: arithmetic marked precise compiles with warning OSH5205, because WGSL cannot forbid a fused multiply-add.

0.3.0-preview.1

  • Push constants: [[vk::push_constant]] on a struct-typed global or a ConstantBuffer<T> compiles on every target. SPIR-V gets a PushConstant block whose member offsets equal DXC's (vector-relaxed std430, not the cbuffer rules); MSL takes it as a constant T& at a Metal buffer index (set it with setVertexBytes, setFragmentBytes or setBytes); WGSL binds it as a uniform buffer at the next free binding of the highest bind group (WebGPU has no push constants). Reflection gains ShaderReflection.PushConstants (layout, stages, per-target slots) and EntryPointInfo.PushConstant; the generated class gains a PushConstants struct (offsets and Size equal to SPIR-V) and Bindings.PushConstants (Size, Stages for the VkPushConstantRange, MslIndex, WgslGroup/WgslBinding). An entry point that reads two blocks is OSH0333 at the second block; a block over 128 bytes (Vulkan's guaranteed maxPushConstantsSize) is warning OSH0334; [[vk::push_constant]] on a cbuffer is OSH0333 (DXC rejects it too).
  • Build package: OneShaderTargets (SpirV;Msl;Wgsl, default SpirV; per-item Targets metadata) embeds Metal and WGSL sources next to the SPIR-V. Entry-point classes gain Msl, MslFunctionName, MslVertexBufferIndex, Wgsl and WgslEntryPoint; resource bindings gain MslIndex / MslCounterIndex and ResourceBinding.MetalIndex. A target's refusal (for example OSH5202 on WGSL) is a build error on the HLSL line only when that target is selected; unknown target names are OSH0903.
  • New WGSL target (CompileTarget.Wgsl): vertex, fragment and compute entry points as WGSL text for WebGPU, with the same buffer byte layouts as the SPIR-V target. See docs/wgsl-backend.md for what is not available.
  • New: Metal Shading Language output (CompileTarget.Msl, MslBackend): one source file per entry point with the same buffer byte layout as the SPIR-V output, Metal argument indices in reflection, and tools/validate-msl.sh to check the files with Apple's compiler. See docs/msl-backend.md.

0.2.0-preview.1

  • Optimizer, on by default (CompileOptions.Optimize): inlining, SSA promotion, constant folding, common-subexpression and dead-code elimination, control-flow simplification. SPIR-V is about 38% smaller than before and close to DXC's size.

  • HLSL 2021 language features: function and struct templates, member functions and this, single inheritance, operator overloads, sizeof, static locals, resource-typed locals and parameters.

  • Bounded resource arrays end to end (Texture2D T[4], SamplerState S[2], arrays of structured buffers and constant buffers): constant and run-time indexing, NonUniformResourceIndex, elements passed to functions, reflection and generated Count. Unbounded arrays are a clear error.

  • AppendStructuredBuffer and ConsumeStructuredBuffer (Append, Consume, GetDimensions) with the hidden counter exposed in reflection (counter records) and in the generated bindings (CounterBinding, a <name>.counter entry in All).

  • A constant buffer may share its name with one of its members, and a whole ConstantBuffer<S> can be copied into a struct local (S s = cb;).

  • Static struct members, member functions defined outside their struct, template argument deduction through struct templates (Box<T>), and integer literal arithmetic in 64 bits ((1 << 31) >> 3 is 0x10000000, as in DXC).

  • A resource array occupies one binding number (it used to take one per element when numbering unannotated resources).

  • Stage inputs and outputs may be arrays and matrices (one location per element or row, like DXC), and SV_ClipDistance / SV_CullDistance work in vertex outputs and pixel inputs, as scalars, vectors or arrays.

  • StructuredBuffer<float3> and other elements whose size is not a multiple of their alignment use the aligned stride (16 bytes for float3), which Vulkan validation requires; bool members of constant and structured buffers are stored as 32-bit integers.

  • New: Texture2D.CalculateLevelOfDetail that validates, GatherCmp/GatherCmpRed on 2D, array and cube textures, f16tof32, f32tof16, faceforward, log10.

  • Overlapping packoffset members are an error (OSH0332) instead of invalid SPIR-V.

  • ShaderCompiler.Compile turns an unexpected internal exception into error OSH7000 on line 1 of the file instead of throwing; the build task reports internal errors at line 1 as well.

  • Hot reload support: CompileResult.Files lists the files a compile read (for file watchers), ShaderCompiler.CompileFile compiles from disk and reports an unreadable file as diagnostic OSH0003, and InterfaceCompatibility.Compare tells whether a recompiled shader still matches the generated C# (constant-buffer offsets and types, vertex inputs, bindings, entry points, thread-group sizes), listing each difference. Generated shader classes gain a Reflection member with the reflection they were generated from. Concurrent Compile calls are tested to give the same bytes as serial ones. samples/HotReload shows the loop.

0.1.0-preview.1

First preview.

  • HLSL compiler for vertex, pixel and compute shaders (modern HLSL, HLSL 2021): preprocessor, constant buffers, textures and samplers, structured and byte-address buffers, storage images, groupshared memory, barriers and atomics.
  • SPIR-V output for Vulkan 1.2, with reflection; buffer layouts and descriptor bindings follow DXC's Vulkan conventions.
  • MSBuild integration: *.hlsl compiled during dotnet build, errors on the HLSL line, embedded SPIR-V and generated C# structs, vertex inputs and binding constants.
  • Known limits: SPIR-V only; single resources only (no resource arrays); no templates, member functions, operator overloads or wave intrinsics yet; dotnet build only.