Skip to content

Commit dfa8728

Browse files
committed
Add a build-time package which generates shader variants.
1 parent 34f4f7a commit dfa8728

File tree

11 files changed

+412
-5
lines changed

11 files changed

+412
-5
lines changed

build-full-package.ps1

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
param (
22
[string]$configuration = "Release",
3-
[Parameter(Mandatory=$true)][string]$tag,
3+
[Parameter(Mandatory=$true)][string]$version,
44
[switch]$public
55
)
66

7-
Write-Host Building $configuration NuGet package for tag $tag...
7+
Write-Host Building $configuration NuGet package for version $version...
8+
$tag = "v"+$version
89
if (!$public)
910
{
1011
Write-Host This is a development build. Pass "-public" to remove the git commit from the package ID.
@@ -113,3 +114,11 @@ Write-Host Generating NuGet package...
113114
dotnet restore src\Veldrid.SPIRV\Veldrid.SPIRV.csproj
114115

115116
dotnet msbuild src\Veldrid.SPIRV\Veldrid.SPIRV.csproj /p:Configuration=$configuration /t:Pack /p:NativeAssetsPath=$PSScriptRoot/download/ /p:PublicRelease=$public
117+
118+
dotnet restore src\Veldrid.SPIRV.VariantCompiler\Veldrid.SPIRV.VariantCompiler.csproj --source bin\Packages\Release /p:VeldridSPIRVVersion=$version --no-cache
119+
120+
dotnet msbuild src\Veldrid.SPIRV.VariantCompiler\Veldrid.SPIRV.VariantCompiler.csproj /p:Configuration=$configuration /p:PublicRelease=$public /p:VeldridSPIRVVersion=$version
121+
122+
dotnet restore src\Veldrid.SPIRV.BuildTools\Veldrid.SPIRV.BuildTools.csproj --source bin\Packages\Release
123+
124+
dotnet msbuild src\Veldrid.SPIRV.BuildTools\Veldrid.SPIRV.BuildTools.csproj /p:Configuration=$configuration /t:Pack /p:PublicRelease=$public /p:VeldridSPIRVVersion=$version
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<Project>
2+
<Target Name="GetFilesToPack" AfterTargets="CoreCompile">
3+
<ItemGroup>
4+
<Content Include="$(PublishedToolPath)\**\*">
5+
<Pack>true</Pack>
6+
<PackagePath>build</PackagePath>
7+
</Content>
8+
</ItemGroup>
9+
</Target>
10+
</Project>
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netcoreapp2.0</TargetFramework>
5+
<IncludeBuildOutput>false</IncludeBuildOutput>
6+
<PublishedToolPath>$([System.IO.Path]::GetFullPath('$(RepositoryRootDirectory)bin\$(Configuration)'))\Veldrid.SPIRV.VariantCompiler\netcoreapp2.2\publish</PublishedToolPath>
7+
8+
<!-- Nuget info -->
9+
<PackageId>Veldrid.SPIRV.BuildTools</PackageId>
10+
<Description>Build-time plugin which compiles multiple shader variants, as defined in a configuration file.</Description>
11+
<PackageTags>Veldrid Shader SPIR-V GLSL HLSL MSL ESSL Graphics OpenGL Vulkan Direct3D Metal Game</PackageTags>
12+
</PropertyGroup>
13+
14+
<ItemGroup>
15+
<Content Include="Veldrid.SPIRV.BuildTools.targets">
16+
<Pack>true</Pack>
17+
<PackagePath>build</PackagePath>
18+
</Content>
19+
</ItemGroup>
20+
21+
<ItemGroup>
22+
<ProjectReference Include="$(MSBuildThisFileDirectory)..\Veldrid.SPIRV.VariantCompiler\Veldrid.SPIRV.VariantCompiler.csproj">
23+
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
24+
<SkipGetTargetFrameworkProperties>true</SkipGetTargetFrameworkProperties>
25+
</ProjectReference>
26+
</ItemGroup>
27+
28+
</Project>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<Project>
2+
3+
<PropertyGroup>
4+
<_VSPV_ToolExePath>$(MSBuildThisFileDirectory)Veldrid.SPIRV.VariantCompiler.dll</_VSPV_ToolExePath>
5+
</PropertyGroup>
6+
7+
<Target Name="CompileShaderVariants" AfterTargets="ResolveReferences">
8+
<Message Text="Compiling shader variants..." />
9+
10+
<Error Condition="'$(_VSPV_ToolExePath)' == ''" Text="The Veldrid.SPIRV variant compiler tool was not located or set." />
11+
<Error Condition="'$(ShaderOutputPath)' == ''" Text="ShaderOutputPath must be set." />
12+
<Error Condition="'$(ShaderVariantDef)' == ''" Text="ShaderVariantDef must be set to a JSON file defining shader variants." />
13+
14+
<PropertyGroup>
15+
<_VSPV_ToolArgs>$(_VSPV_ToolArgs) @(ShaderSourceDir->'--search-path %(Identity)')</_VSPV_ToolArgs>
16+
<_VSPV_ToolArgs>$(_VSPV_ToolArgs) --output-path $(ShaderOutputPath)</_VSPV_ToolArgs>
17+
<_VSPV_ToolArgs>$(_VSPV_ToolArgs) --set $(ShaderVariantDef)</_VSPV_ToolArgs>
18+
</PropertyGroup>
19+
20+
<PropertyGroup>
21+
<_VSPV_ContinueOnError>false</_VSPV_ContinueOnError>
22+
<_VSPV_ContinueOnError Condition="'$(SGIgnoreErrors)' == 'true' Or '$(DesignTimeBuild)' == 'true'">true</_VSPV_ContinueOnError>
23+
</PropertyGroup>
24+
25+
<Exec Command="dotnet &quot;$(_VSPV_ToolExePath)&quot; $(_VSPV_ToolArgs)"
26+
WorkingDirectory="$(MSBuildProjectDirectory)"
27+
StandardOutputImportance="high"
28+
ContinueOnError="$(_VSPV_ContinueOnError)" />
29+
</Target>
30+
31+
</Project>
Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
6+
namespace Veldrid.SPIRV
7+
{
8+
public class VariantStageDescription
9+
{
10+
public ShaderStages Stage { get; }
11+
public string FileName { get; }
12+
13+
public VariantStageDescription(ShaderStages stage, string fileName)
14+
{
15+
Stage = stage;
16+
FileName = fileName;
17+
}
18+
}
19+
20+
public class ShaderVariantDescription
21+
{
22+
public string Name { get; }
23+
public VariantStageDescription[] Shaders { get; }
24+
public MacroDefinition[] Macros { get; }
25+
public CrossCompileOptions CrossCompileOptions { get; }
26+
public CrossCompileTarget[] Targets { get; }
27+
28+
public ShaderVariantDescription(
29+
string name,
30+
VariantStageDescription[] shaders,
31+
MacroDefinition[] macros,
32+
CrossCompileOptions crossCompileOptions,
33+
CrossCompileTarget[] targets)
34+
{
35+
Name = name;
36+
Shaders = shaders;
37+
Macros = macros;
38+
CrossCompileOptions = crossCompileOptions;
39+
Targets = targets;
40+
}
41+
}
42+
43+
public class VariantCompiler
44+
{
45+
private readonly List<string> _shaderSearchPaths = new List<string>();
46+
private readonly string _outputPath;
47+
48+
public VariantCompiler(List<string> shaderSearchPaths, string outputPath)
49+
{
50+
_shaderSearchPaths = shaderSearchPaths;
51+
_outputPath = outputPath;
52+
}
53+
54+
public void Compile(ShaderVariantDescription variant)
55+
{
56+
if (variant.Shaders.Length == 1)
57+
{
58+
if (variant.Shaders[0].Stage == ShaderStages.Vertex) { CompileVertexFragment(variant); }
59+
if (variant.Shaders[0].Stage == ShaderStages.Compute) { CompileCompute(variant); }
60+
else
61+
{
62+
throw new SpirvCompilationException(
63+
$"Variant \"{variant.Name}\" has an unsupported set of shader stages.");
64+
}
65+
}
66+
if (variant.Shaders.Length == 2)
67+
{
68+
bool hasVertex = false;
69+
bool hasFragment = false;
70+
foreach (var shader in variant.Shaders)
71+
{
72+
hasVertex |= shader.Stage == ShaderStages.Vertex;
73+
hasFragment |= shader.Stage == ShaderStages.Fragment;
74+
}
75+
76+
if (!hasVertex)
77+
{
78+
throw new SpirvCompilationException($"Variant \"{variant.Name}\" is missing a vertex shader.");
79+
}
80+
if (!hasFragment)
81+
{
82+
throw new SpirvCompilationException($"Variant \"{variant.Name}\" is missing a fragment shader.");
83+
}
84+
85+
CompileVertexFragment(variant);
86+
}
87+
}
88+
89+
private void CompileVertexFragment(ShaderVariantDescription variant)
90+
{
91+
List<Exception> compilationExceptions = new List<Exception>();
92+
byte[] vsBytes = null;
93+
byte[] fsBytes = null;
94+
95+
string vertexFileName = variant.Shaders.FirstOrDefault(vsd => vsd.Stage == ShaderStages.Vertex)?.FileName;
96+
if (vertexFileName != null)
97+
{
98+
try
99+
{
100+
vsBytes = CompileToSpirv(variant, vertexFileName, ShaderStages.Vertex);
101+
}
102+
catch (Exception e)
103+
{
104+
compilationExceptions.Add(e);
105+
}
106+
}
107+
108+
string fragmentFileName = variant.Shaders.FirstOrDefault(vsd => vsd.Stage == ShaderStages.Fragment)?.FileName;
109+
if (fragmentFileName != null)
110+
{
111+
try
112+
{
113+
fsBytes = CompileToSpirv(variant, fragmentFileName, ShaderStages.Fragment);
114+
}
115+
catch (Exception e)
116+
{
117+
compilationExceptions.Add(e);
118+
}
119+
}
120+
121+
if (compilationExceptions.Count > 0)
122+
{
123+
throw new AggregateException(
124+
$"Errors were encountered when compiling from GLSL to SPIR-V.",
125+
compilationExceptions);
126+
}
127+
128+
foreach (CrossCompileTarget target in variant.Targets)
129+
{
130+
try
131+
{
132+
VertexFragmentCompilationResult result = SpirvCompilation.CompileVertexFragment(
133+
vsBytes,
134+
fsBytes,
135+
target,
136+
variant.CrossCompileOptions);
137+
if (result.VertexShader != null)
138+
{
139+
string vsPath = Path.Combine(_outputPath, $"{variant.Name}_Vertex.{GetExtension(target)}");
140+
File.WriteAllText(vsPath, result.VertexShader);
141+
}
142+
if (result.FragmentShader != null)
143+
{
144+
string fsPath = Path.Combine(_outputPath, $"{variant.Name}_Fragment.{GetExtension(target)}");
145+
File.WriteAllText(fsPath, result.FragmentShader);
146+
}
147+
}
148+
catch (Exception e)
149+
{
150+
compilationExceptions.Add(e);
151+
}
152+
}
153+
154+
if (compilationExceptions.Count > 0)
155+
{
156+
throw new AggregateException($"Errors were encountered when compiling shader variant(s).", compilationExceptions);
157+
}
158+
}
159+
160+
private string GetExtension(CrossCompileTarget target)
161+
{
162+
switch (target)
163+
{
164+
case CrossCompileTarget.HLSL:
165+
return "hlsl";
166+
case CrossCompileTarget.GLSL:
167+
return "glsl";
168+
case CrossCompileTarget.ESSL:
169+
return "essl";
170+
case CrossCompileTarget.MSL:
171+
return "metal";
172+
default:
173+
throw new SpirvCompilationException($"Invalid CrossCompileTarget: {target}");
174+
}
175+
}
176+
177+
private byte[] CompileToSpirv(
178+
ShaderVariantDescription variant,
179+
string fileName,
180+
ShaderStages stage)
181+
{
182+
GlslCompileOptions glslOptions = GetOptions(variant);
183+
string glsl = LoadGlsl(fileName);
184+
SpirvCompilationResult result = SpirvCompilation.CompileGlslToSpirv(
185+
glsl,
186+
fileName,
187+
stage,
188+
glslOptions);
189+
string spvPath = Path.Combine(_outputPath, $"{variant.Name}_{stage.ToString()}.spv");
190+
File.WriteAllBytes(spvPath, result.SpirvBytes);
191+
return result.SpirvBytes;
192+
}
193+
194+
private GlslCompileOptions GetOptions(ShaderVariantDescription variant)
195+
{
196+
return new GlslCompileOptions(true, variant.Macros);
197+
}
198+
199+
private string LoadGlsl(string fileName)
200+
{
201+
if (fileName == null) { return null; }
202+
203+
foreach (string searchPath in _shaderSearchPaths)
204+
{
205+
string fullPath = Path.Combine(searchPath, fileName);
206+
if (File.Exists(fullPath))
207+
{
208+
return File.ReadAllText(fullPath);
209+
}
210+
}
211+
212+
throw new FileNotFoundException($"Unable to find shader file \"{fileName}\".");
213+
}
214+
215+
private void CompileCompute(ShaderVariantDescription variant)
216+
{
217+
byte[] csBytes = CompileToSpirv(variant, variant.Shaders[0].FileName, ShaderStages.Compute);
218+
219+
List<Exception> compilationExceptions = new List<Exception>();
220+
foreach (CrossCompileTarget target in variant.Targets)
221+
{
222+
try
223+
{
224+
ComputeCompilationResult result = SpirvCompilation.CompileCompute(csBytes, target, variant.CrossCompileOptions);
225+
string csPath = Path.Combine(_outputPath, $"{variant.Name}_Compute.{GetExtension(target)}");
226+
File.WriteAllText(csPath, result.ComputeShader);
227+
}
228+
catch (Exception e)
229+
{
230+
compilationExceptions.Add(e);
231+
}
232+
}
233+
234+
if (compilationExceptions.Count > 0)
235+
{
236+
throw new AggregateException($"Errors were encountered when compiling shader variant(s).", compilationExceptions);
237+
}
238+
}
239+
}
240+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
using McMaster.Extensions.CommandLineUtils;
2+
using Newtonsoft.Json;
3+
using Newtonsoft.Json.Converters;
4+
using System;
5+
using System.Collections.Generic;
6+
using System.IO;
7+
8+
namespace Veldrid.SPIRV
9+
{
10+
public class Program
11+
{
12+
public static void Main(string[] args)
13+
{
14+
CommandLineApplication.Execute<Program>(args);
15+
}
16+
17+
[Option("--search-path", "The set of directories to search for shader source files.", CommandOptionType.MultipleValue)]
18+
public string[] SearchPaths { get; }
19+
20+
[Option("--output-path", "The directory where compiled files are placed.", CommandOptionType.SingleValue)]
21+
public string OutputPath { get; }
22+
23+
[Option("--set", "The path to the JSON file containing shader variant definitions to compile.", CommandOptionType.SingleValue)]
24+
public string SetDefinitionPath { get; }
25+
26+
public void OnExecute()
27+
{
28+
if (!Directory.Exists(OutputPath))
29+
{
30+
Directory.CreateDirectory(OutputPath);
31+
}
32+
33+
ShaderVariantDescription[] descs;
34+
JsonSerializer serializer = new JsonSerializer();
35+
serializer.Formatting = Formatting.Indented;
36+
StringEnumConverter enumConverter = new StringEnumConverter();
37+
serializer.Converters.Add(enumConverter);
38+
using (StreamReader sr = File.OpenText(SetDefinitionPath))
39+
using (JsonTextReader jtr = new JsonTextReader(sr))
40+
{
41+
descs = serializer.Deserialize<ShaderVariantDescription[]>(jtr);
42+
}
43+
44+
VariantCompiler compiler = new VariantCompiler(new List<string>(SearchPaths), OutputPath);
45+
foreach (ShaderVariantDescription desc in descs)
46+
{
47+
compiler.Compile(desc);
48+
}
49+
}
50+
}
51+
}

0 commit comments

Comments
 (0)