Skip to content

Commit 6384fd4

Browse files
committed
Shader include support
1 parent 997d2a4 commit 6384fd4

23 files changed

+122
-211
lines changed

Nanoforge/Render/Materials/MaterialPipeline.cs

+5-10
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Threading;
66
using Nanoforge.Render.Misc;
77
using Nanoforge.Render.Resources;
8+
using Serilog;
89
using Silk.NET.Core.Native;
910
using Silk.NET.Shaderc;
1011
using Silk.NET.Vulkan;
@@ -27,11 +28,9 @@ public class MaterialPipeline
2728

2829
private Shader? _vertexShader;
2930
private string VertexShaderPath => $"{BuildConfig.ShadersDirectory}{Name}.vert";
30-
private DateTime _vertexShaderLastWriteTime;
3131

3232
private Shader? _fragmentShader;
3333
private string FragmentShaderPath => $"{BuildConfig.ShadersDirectory}{Name}.frag";
34-
private DateTime _fragmentShaderLastWriteTime;
3534

3635
private bool _disableFaceCulling;
3736

@@ -43,8 +42,6 @@ public MaterialPipeline(RenderContext context, string name, RenderPass renderPas
4342
_renderPass = renderPass;
4443
_stride = stride;
4544
_attributes = attributes.ToArray();
46-
_vertexShaderLastWriteTime = File.GetLastWriteTime(VertexShaderPath);
47-
_fragmentShaderLastWriteTime = File.GetLastWriteTime(FragmentShaderPath);
4845
_disableFaceCulling = disableFaceCulling;
4946

5047
Init();
@@ -293,19 +290,17 @@ private unsafe void CreateGraphicsPipeline()
293290

294291
public void ReloadEditedShaders()
295292
{
293+
//TODO: Move shader change checks to function in Shader so it can check included files too
296294
DateTime vertexShaderWriteTime = File.GetLastWriteTime(VertexShaderPath);
297295
DateTime fragmentShaderWriteTime = File.GetLastWriteTime(FragmentShaderPath);
298-
if (vertexShaderWriteTime != _vertexShaderLastWriteTime || fragmentShaderWriteTime != _fragmentShaderLastWriteTime)
296+
//if (vertexShaderWriteTime != _vertexShaderLastWriteTime || fragmentShaderWriteTime != _fragmentShaderLastWriteTime)
297+
if (_vertexShader is { SourceFilesEdited: true } || _fragmentShader is { SourceFilesEdited: true })
299298
{
300-
//TODO: Add NF logging
301-
Console.WriteLine($"Reloading shaders for {Name}");
299+
Log.Information($"Reloading shaders for {Name}");
302300
Thread.Sleep(250); //Wait a moment to make sure the shader isn't being saved by another process while we're loading it. Stupid fix, but it works.
303301
_context.Vk.QueueWaitIdle(_context.GraphicsQueue); //Wait for graphics queue so we know the pipeline isn't in use when we destroy it
304302
Destroy();
305303
Init();
306-
307-
_vertexShaderLastWriteTime = vertexShaderWriteTime;
308-
_fragmentShaderLastWriteTime = fragmentShaderWriteTime;
309304
}
310305
}
311306

Nanoforge/Render/Resources/Shader.cs

+67-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.IO;
4+
using System.Linq;
5+
using System.Runtime.CompilerServices;
6+
using System.Runtime.InteropServices;
37
using System.Text;
48
using Serilog;
59
using Silk.NET.Core.Native;
@@ -19,6 +23,17 @@ public class Shader
1923

2024
public ShaderModule Module { get; private set; }
2125

26+
public const int MaxIncludeDepth = 10;
27+
28+
private List<(string path, DateTime writeTime)> _fileWriteTimes = new();
29+
30+
public bool SourceFilesEdited
31+
{
32+
get
33+
{
34+
return _fileWriteTimes.Any(tuple => tuple.writeTime != File.GetLastWriteTime(tuple.path));
35+
}
36+
}
2237

2338
public Shader(RenderContext context, string shaderFilePath, ShaderKind shaderKind, bool optimize = true, string entryPoint = "main")
2439
{
@@ -72,6 +87,9 @@ private unsafe byte[] CompileShaderFile(string shaderPath, string entryPoint, Sh
7287
CompilationResult* compilationResult = null;
7388
try
7489
{
90+
_fileWriteTimes.Clear();
91+
_fileWriteTimes.Add((shaderPath, File.GetLastWriteTime(shaderPath)));
92+
7593
string shaderText = File.ReadAllText(shaderPath);
7694
byte[] inputFileNameBytes = Encoding.ASCII.GetBytes(Path.GetFileName(shaderPath));
7795
byte[] entryPointNameBytes = Encoding.ASCII.GetBytes(entryPoint);
@@ -83,7 +101,12 @@ private unsafe byte[] CompileShaderFile(string shaderPath, string entryPoint, Sh
83101
{
84102
shaderc.CompileOptionsSetOptimizationLevel(compileOptions, OptimizationLevel.Performance);
85103
}
86-
104+
105+
//Set functions for resolving #includes in shaders
106+
var resolverFn = new PfnIncludeResolveFn(ShaderIncludeResolve);
107+
var releaseFn = new PfnIncludeResultReleaseFn(ShaderIncludeResultRelease);
108+
shaderc.CompileOptionsSetIncludeCallbacks(compileOptions, resolverFn, releaseFn, null);
109+
87110
fixed (byte* inputFileName = inputFileNameBytes)
88111
{
89112
fixed (byte* entryPointName = entryPointNameBytes)
@@ -133,6 +156,49 @@ private unsafe byte[] CompileShaderFile(string shaderPath, string entryPoint, Sh
133156
}
134157
}
135158
}
159+
160+
//This and ShaderIncludeReleaseResult() are equivalent to the virtual functions on shaderc::CompileOptions::IncluderInterface in the C++ shaderc API
161+
private unsafe IncludeResult* ShaderIncludeResolve(void* interfacePtr, byte* requestedSourcePtr, int includeTypeInt, byte* requestingSourcePtr, UIntPtr includeDepthUintPtr)
162+
{
163+
string requestedSource = Marshal.PtrToStringAnsi((IntPtr)requestedSourcePtr) ?? throw new Exception("Failed to resolve requestedSource in shader include handler.");
164+
ulong includeDepth = includeDepthUintPtr;
165+
if (includeDepth > MaxIncludeDepth)
166+
{
167+
Log.Error($"Exceeded maximum include depth of {MaxIncludeDepth} while compiling shader '{Name}'.");
168+
return null;
169+
}
170+
171+
string includeFullPath = $"{BuildConfig.ShadersDirectory}{requestedSource}";
172+
if (requestedSource == Name || _fileWriteTimes.Any(tuple => tuple.path == includeFullPath))
173+
{
174+
Log.Error($"Circular include detected when compiling shader '{Name}'.");
175+
return null;
176+
}
177+
178+
if (!File.Exists(includeFullPath))
179+
{
180+
Log.Error($"Failed to find shader at include path '{requestedSource}'.");
181+
return null;
182+
}
183+
184+
IncludeResult* includeResult = (IncludeResult*)SilkMarshal.Allocate(Unsafe.SizeOf<IncludeResult>());
185+
includeResult->SourceName = (byte*)SilkMarshal.StringToPtr(requestedSource);
186+
includeResult->SourceNameLength = (uint)requestedSource.Length;
187+
188+
string includedFileContents = File.ReadAllText(includeFullPath);
189+
includeResult->Content = (byte*)SilkMarshal.StringToPtr(includedFileContents);
190+
includeResult->ContentLength = (uint)includedFileContents.Length;
191+
192+
_fileWriteTimes.Add((includeFullPath, File.GetLastWriteTime(includeFullPath)));
193+
return includeResult;
194+
}
195+
196+
private unsafe void ShaderIncludeResultRelease(void* interfacePtr, IncludeResult* data)
197+
{
198+
SilkMarshal.Free((nint)data->SourceName);
199+
SilkMarshal.Free((nint)data->Content);
200+
SilkMarshal.Free((IntPtr)data);
201+
}
136202

137203
private ShaderModule LoadAndCompileShaderFile(string shaderPath, string entryPoint, ShaderKind shaderKind, bool optimize = true)
138204
{
+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
2+
layout(binding = 0) uniform UniformBufferObject
3+
{
4+
mat4 view;
5+
mat4 proj;
6+
vec4 cameraPos;
7+
} ubo;
8+
9+
struct ObjectData
10+
{
11+
mat4 model;
12+
vec4 worldPos;
13+
};
14+
15+
layout(std140, binding = 11) readonly buffer ObjectBuffer
16+
{
17+
ObjectData objects[];
18+
} objectBuffer;

Nanoforge/assets/shaders/Linelist.frag

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#version 460
2+
#include "Constants.glsl"
23

34
layout(location = 0) in float fragSize;
45
layout(location = 1) in vec4 fragColor;

Nanoforge/assets/shaders/Linelist.vert

+1-7
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,5 @@
11
#version 460
2-
3-
layout(binding = 0) uniform UniformBufferObject
4-
{
5-
mat4 view;
6-
mat4 proj;
7-
vec4 cameraPos;
8-
} ubo;
2+
#include "Constants.glsl"
93

104
layout(location = 0) in vec4 inPosAndSize;
115
layout(location = 1) in vec4 inColor;

Nanoforge/assets/shaders/Pixlit1Uv.frag

+5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
#version 460
2+
#include "Constants.glsl"
23

4+
layout(early_fragment_tests) in;
5+
6+
//layout(binding = 0) uniform sampler texSampler;
7+
//layout(binding = 1) uniform texture2D textures[8192];
38
layout(binding = 1) uniform sampler2D texSampler;
49

510
layout(location = 0) in vec2 fragTexCoord;

Nanoforge/assets/shaders/Pixlit1Uv.vert

+1-18
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,5 @@
11
#version 460
2-
3-
layout(binding = 0) uniform UniformBufferObject
4-
{
5-
mat4 view;
6-
mat4 proj;
7-
vec4 cameraPos;
8-
} ubo;
9-
10-
struct ObjectData
11-
{
12-
mat4 model;
13-
vec4 worldPos;
14-
};
15-
16-
layout(std140, binding = 11) readonly buffer ObjectBuffer
17-
{
18-
ObjectData objects[];
19-
} objectBuffer;
2+
#include "Constants.glsl"
203

214
layout(location = 0) in vec3 inPosition;
225
layout(location = 1) in vec4 inTangent;

Nanoforge/assets/shaders/Pixlit1UvNmap.frag

+3-7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
#version 460
2+
#include "Constants.glsl"
3+
4+
layout(early_fragment_tests) in;
25

36
layout(binding = 1) uniform sampler2D diffuseSampler;
47
layout(binding = 2) uniform sampler2D normalSampler;
@@ -11,13 +14,6 @@ layout(location = 3) in vec4 fragNormal;
1114

1215
layout(location = 0) out vec4 outColor;
1316

14-
layout(binding = 0) uniform UniformBufferObject
15-
{
16-
mat4 view;
17-
mat4 proj;
18-
vec4 cameraPos;
19-
} ubo;
20-
2117
void main()
2218
{
2319
//TODO: Change this to be provided by the per-frame push constant or a per-frame constants buffer

Nanoforge/assets/shaders/Pixlit1UvNmap.vert

+1-18
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,5 @@
11
#version 460
2-
3-
layout(binding = 0) uniform UniformBufferObject
4-
{
5-
mat4 view;
6-
mat4 proj;
7-
vec4 cameraPos;
8-
} ubo;
9-
10-
struct ObjectData
11-
{
12-
mat4 model;
13-
vec4 worldPos;
14-
};
15-
16-
layout(std140, binding = 11) readonly buffer ObjectBuffer
17-
{
18-
ObjectData objects[];
19-
} objectBuffer;
2+
#include "Constants.glsl"
203

214
layout(location = 0) in vec3 inPosition;
225
layout(location = 1) in vec4 inNormal;

Nanoforge/assets/shaders/Pixlit2UvNmap.frag

+3-7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
#version 460
2+
#include "Constants.glsl"
3+
4+
layout(early_fragment_tests) in;
25

36
layout(binding = 1) uniform sampler2D diffuseSampler;
47
layout(binding = 2) uniform sampler2D normalSampler;
@@ -11,13 +14,6 @@ layout(location = 3) in vec4 fragNormal;
1114

1215
layout(location = 0) out vec4 outColor;
1316

14-
layout(binding = 0) uniform UniformBufferObject
15-
{
16-
mat4 view;
17-
mat4 proj;
18-
vec4 cameraPos;
19-
} ubo;
20-
2117
void main()
2218
{
2319
//TODO: Change this to be provided by the per-frame push constant or a per-frame constants buffer

Nanoforge/assets/shaders/Pixlit2UvNmap.vert

+1-18
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,5 @@
11
#version 460
2-
3-
layout(binding = 0) uniform UniformBufferObject
4-
{
5-
mat4 view;
6-
mat4 proj;
7-
vec4 cameraPos;
8-
} ubo;
9-
10-
struct ObjectData
11-
{
12-
mat4 model;
13-
vec4 worldPos;
14-
};
15-
16-
layout(std140, binding = 11) readonly buffer ObjectBuffer
17-
{
18-
ObjectData objects[];
19-
} objectBuffer;
2+
#include "Constants.glsl"
203

214
layout(location = 0) in vec3 inPosition;
225
layout(location = 1) in vec4 inNormal;

Nanoforge/assets/shaders/Pixlit3UvNmap.frag

+3-7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
#version 460
2+
#include "Constants.glsl"
3+
4+
layout(early_fragment_tests) in;
25

36
layout(binding = 1) uniform sampler2D diffuseSampler;
47
layout(binding = 2) uniform sampler2D normalSampler;
@@ -11,13 +14,6 @@ layout(location = 3) in vec4 fragNormal;
1114

1215
layout(location = 0) out vec4 outColor;
1316

14-
layout(binding = 0) uniform UniformBufferObject
15-
{
16-
mat4 view;
17-
mat4 proj;
18-
vec4 cameraPos;
19-
} ubo;
20-
2117
void main()
2218
{
2319
//TODO: Change this to be provided by the per-frame push constant or a per-frame constants buffer

Nanoforge/assets/shaders/Pixlit3UvNmap.vert

+1-18
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,5 @@
11
#version 460
2-
3-
layout(binding = 0) uniform UniformBufferObject
4-
{
5-
mat4 view;
6-
mat4 proj;
7-
vec4 cameraPos;
8-
} ubo;
9-
10-
struct ObjectData
11-
{
12-
mat4 model;
13-
vec4 worldPos;
14-
};
15-
16-
layout(std140, binding = 11) readonly buffer ObjectBuffer
17-
{
18-
ObjectData objects[];
19-
} objectBuffer;
2+
#include "Constants.glsl"
203

214
layout(location = 0) in vec3 inPosition;
225
layout(location = 1) in vec4 inNormal;

Nanoforge/assets/shaders/Pixlit4UvNmap.frag

+3-7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
#version 460
2+
#include "Constants.glsl"
3+
4+
layout(early_fragment_tests) in;
25

36
layout(binding = 1) uniform sampler2D diffuseSampler;
47
layout(binding = 2) uniform sampler2D normalSampler;
@@ -11,13 +14,6 @@ layout(location = 3) in vec4 fragNormal;
1114

1215
layout(location = 0) out vec4 outColor;
1316

14-
layout(binding = 0) uniform UniformBufferObject
15-
{
16-
mat4 view;
17-
mat4 proj;
18-
vec4 cameraPos;
19-
} ubo;
20-
2117
void main()
2218
{
2319
//TODO: Change this to be provided by the per-frame push constant or a per-frame constants buffer

0 commit comments

Comments
 (0)