@@ -21,6 +21,8 @@ using namespace nbl;
21
21
using namespace nbl ::asset;
22
22
using Microsoft::WRL::ComPtr;
23
23
24
+ static constexpr const wchar_t * SHADER_MODEL_PROFILE = L" XX_6_6" ;
25
+
24
26
namespace nbl ::asset::hlsl::impl
25
27
{
26
28
struct DXC {
@@ -52,43 +54,61 @@ CHLSLCompiler::~CHLSLCompiler()
52
54
}
53
55
54
56
static tcpp::IInputStream* getInputStreamInclude (
55
- const IShaderCompiler::CIncludeFinder* _inclFinder,
56
- const system::ISystem* _fs,
57
- uint32_t _maxInclCnt,
58
- const char * _requesting_source,
59
- const char * _requested_source,
60
- bool _type // true for #include "string"; false for #include <string>
57
+ const IShaderCompiler::CIncludeFinder* inclFinder,
58
+ const system::ISystem* fs,
59
+ uint32_t maxInclCnt,
60
+ const char * requestingSource,
61
+ const char * requestedSource,
62
+ bool isRelative, // true for #include "string"; false for #include <string>
63
+ uint32_t lexerLineIndex,
64
+ uint32_t leadingLinesImports,
65
+ std::vector<std::pair<uint32_t , std::string>>& includeStack
61
66
)
62
67
{
63
68
std::string res_str;
64
69
65
70
std::filesystem::path relDir;
66
- const bool reqFromBuiltin = builtin::hasPathPrefix (_requesting_source );
67
- const bool reqBuiltin = builtin::hasPathPrefix (_requested_source );
71
+ const bool reqFromBuiltin = builtin::hasPathPrefix (requestingSource );
72
+ const bool reqBuiltin = builtin::hasPathPrefix (requestedSource );
68
73
if (!reqFromBuiltin && !reqBuiltin)
69
74
{
70
75
// While #includ'ing a builtin, one must specify its full path (starting with "nbl/builtin" or "/nbl/builtin").
71
76
// This rule applies also while a builtin is #includ`ing another builtin.
72
77
// While including a filesystem file it must be either absolute path (or relative to any search dir added to asset::iIncludeHandler; <>-type),
73
78
// or path relative to executable's working directory (""-type).
74
- relDir = std::filesystem::path (_requesting_source ).parent_path ();
79
+ relDir = std::filesystem::path (requestingSource ).parent_path ();
75
80
}
76
- std::filesystem::path name = _type ? (relDir / _requested_source ) : (_requested_source );
81
+ std::filesystem::path name = isRelative ? (relDir / requestedSource ) : (requestedSource );
77
82
78
83
if (std::filesystem::exists (name) && !reqBuiltin)
79
84
name = std::filesystem::absolute (name);
80
85
81
- if (_type )
82
- res_str = _inclFinder ->getIncludeRelative (relDir, _requested_source );
86
+ if (isRelative )
87
+ res_str = inclFinder ->getIncludeRelative (relDir, requestedSource );
83
88
else // shaderc_include_type_standard
84
- res_str = _inclFinder ->getIncludeStandard (relDir, _requested_source );
89
+ res_str = inclFinder ->getIncludeStandard (relDir, requestedSource );
85
90
86
91
if (!res_str.size ()) {
87
92
return new tcpp::StringInputStream (" #error File not found" );
88
93
}
89
94
95
+ // Figure out what line in the current file this #include was
96
+ // That would be the current lexer line, minus the line where the current file was included
97
+ uint32_t lineInCurrentFileWithInclude = lexerLineIndex -
98
+ // if this is 2 includes deep (include within include), subtract leading import lines
99
+ // from the previous include
100
+ (includeStack.size () > 1 ? leadingLinesImports : 0 );
101
+ auto lastItemInIncludeStack = includeStack.back ();
102
+
90
103
IShaderCompiler::disableAllDirectivesExceptIncludes (res_str);
91
- res_str = IShaderCompiler::encloseWithinExtraInclGuards (std::move (res_str), _maxInclCnt, name.string ().c_str ());
104
+ res_str = IShaderCompiler::encloseWithinExtraInclGuards (std::move (res_str), maxInclCnt, name.string ().c_str ());
105
+ res_str = res_str + " \n " +
106
+ IShaderCompiler::PREPROC_DIRECTIVE_DISABLER + " line " + std::to_string (lineInCurrentFileWithInclude - lastItemInIncludeStack.first - 1 ).c_str () + " \" " + lastItemInIncludeStack.second .c_str () + " \"\n " ;
107
+
108
+ // Offset the lines this include takes up for subsequent includes
109
+ includeStack.back ().first += std::count (res_str.begin (), res_str.end (), ' \n ' );
110
+
111
+ includeStack.push_back (std::pair<uint32_t , std::string>(lineInCurrentFileWithInclude, IShaderCompiler::escapeFilename (name.string ())));
92
112
93
113
return new tcpp::StringInputStream (std::move (res_str));
94
114
}
@@ -100,9 +120,9 @@ class DxcCompilationResult
100
120
ComPtr<IDxcBlob> objectBlob;
101
121
ComPtr<IDxcResult> compileResult;
102
122
103
- char * GetErrorMessagesString ()
123
+ std::string GetErrorMessagesString ()
104
124
{
105
- return reinterpret_cast <char *>(errorMessages->GetBufferPointer ());
125
+ return std::string ( reinterpret_cast <char *>(errorMessages->GetBufferPointer ()), errorMessages-> GetBufferSize ());
106
126
}
107
127
};
108
128
@@ -151,9 +171,17 @@ DxcCompilationResult dxcCompile(const CHLSLCompiler* compiler, nbl::asset::hlsl:
151
171
result.compileResult = compileResult;
152
172
result.objectBlob = nullptr ;
153
173
154
- if (!SUCCEEDED (compilationStatus))
174
+ auto errorMessagesString = result.GetErrorMessagesString ();
175
+ if (SUCCEEDED (compilationStatus))
176
+ {
177
+ if (errorMessagesString.length () > 0 )
178
+ {
179
+ options.preprocessorOptions .logger .log (" DXC Compilation Warnings:\n %s" , system::ILogger::ELL_WARNING, errorMessagesString.c_str ());
180
+ }
181
+ }
182
+ else
155
183
{
156
- options.preprocessorOptions .logger .log (result. GetErrorMessagesString () , system::ILogger::ELL_ERROR);
184
+ options.preprocessorOptions .logger .log (" DXC Compilation Failed: \n %s " , system::ILogger::ELL_ERROR, errorMessagesString. c_str () );
157
185
return result;
158
186
}
159
187
@@ -169,13 +197,22 @@ DxcCompilationResult dxcCompile(const CHLSLCompiler* compiler, nbl::asset::hlsl:
169
197
170
198
std::string CHLSLCompiler::preprocessShader (std::string&& code, IShader::E_SHADER_STAGE& stage, const SPreprocessorOptions& preprocessOptions) const
171
199
{
200
+ // Line 1 comes before all the extra defines in the main shader
201
+ insertIntoStart (code, std::ostringstream (std::string (IShaderCompiler::PREPROC_DIRECTIVE_ENABLER) + " line 1\n " ));
202
+
203
+ uint32_t defineLeadingLinesMain = 1 ;
204
+ uint32_t leadingLinesImports = IShaderCompiler::encloseWithinExtraInclGuardsLeadingLines (preprocessOptions.maxSelfInclusionCount + 1u );
172
205
if (preprocessOptions.extraDefines .size ())
173
206
{
174
207
insertExtraDefines (code, preprocessOptions.extraDefines );
208
+ defineLeadingLinesMain += preprocessOptions.extraDefines .size ();
175
209
}
176
210
177
211
IShaderCompiler::disableAllDirectivesExceptIncludes (code);
178
212
213
+ // Keep track of the line in the original file where each #include was on each level of the include stack
214
+ std::vector<std::pair<uint32_t , std::string>> lineOffsetStack = { std::pair<uint32_t , std::string>(defineLeadingLinesMain, preprocessOptions.sourceIdentifier ) };
215
+
179
216
tcpp::StringInputStream codeIs = tcpp::StringInputStream (code);
180
217
tcpp::Lexer lexer (codeIs);
181
218
tcpp::Preprocessor proc (
@@ -188,13 +225,17 @@ std::string CHLSLCompiler::preprocessShader(std::string&& code, IShader::E_SHADE
188
225
{
189
226
return getInputStreamInclude (
190
227
preprocessOptions.includeFinder , m_system.get (), preprocessOptions.maxSelfInclusionCount + 1u ,
191
- preprocessOptions.sourceIdentifier .data (), path.c_str (), !isSystemPath
228
+ preprocessOptions.sourceIdentifier .data (), path.c_str (), !isSystemPath,
229
+ lexer.GetCurrLineIndex (), leadingLinesImports, lineOffsetStack
192
230
);
193
231
}
194
232
else
195
233
{
196
234
return static_cast <tcpp::IInputStream*>(new tcpp::StringInputStream (std::string (" #error No include handler" )));
197
235
}
236
+ },
237
+ [&]() {
238
+ lineOffsetStack.pop_back ();
198
239
}
199
240
);
200
241
@@ -239,6 +280,7 @@ std::string CHLSLCompiler::preprocessShader(std::string&& code, IShader::E_SHADE
239
280
240
281
auto resolvedString = proc.Process ();
241
282
IShaderCompiler::reenableDirectives (resolvedString);
283
+
242
284
return resolvedString;
243
285
}
244
286
@@ -256,7 +298,16 @@ core::smart_refctd_ptr<ICPUShader> CHLSLCompiler::compileToSPIRV(const char* cod
256
298
auto newCode = preprocessShader (code, stage, hlslOptions.preprocessorOptions );
257
299
258
300
// Suffix is the shader model version
259
- std::wstring targetProfile (L" XX_6_2" );
301
+ // TODO: Figure out a way to get the shader model version automatically
302
+ //
303
+ // We can't get it from the DXC library itself, as the different versions and the parsing
304
+ // use a weird lexer based system that resolves to a hash, and all of that is in a scoped variable
305
+ // (lib/DXIL/DxilShaderModel.cpp:83)
306
+ //
307
+ // Another option is trying to fetch it from the commandline tool, either from parsing the help message
308
+ // or from brute forcing every -T option until one isn't accepted
309
+ //
310
+ std::wstring targetProfile (SHADER_MODEL_PROFILE);
260
311
261
312
// Set profile two letter prefix based on stage
262
313
switch (stage) {
@@ -289,29 +340,40 @@ core::smart_refctd_ptr<ICPUShader> CHLSLCompiler::compileToSPIRV(const char* cod
289
340
return nullptr ;
290
341
};
291
342
292
- LPCWSTR arguments[] = {
293
- // These will always be present
343
+ std::vector<LPCWSTR> arguments = {
294
344
L" -spirv" ,
295
345
L" -HV" , L" 2021" ,
296
346
L" -T" , targetProfile.c_str (),
297
-
298
- // These are debug only
299
- DXC_ARG_DEBUG,
300
- L" -Qembed_debug" ,
301
- L" -fspv-debug=vulkan-with-source" ,
302
- L" -fspv-debug=file"
303
347
};
304
348
305
- const uint32_t nonDebugArgs = 5 ;
306
- const uint32_t allArgs = nonDebugArgs + 4 ;
349
+ // If a custom SPIR-V optimizer is specified, use that instead of DXC's spirv-opt.
350
+ // This is how we can get more optimizer options.
351
+ //
352
+ // Optimization is also delegated to SPIRV-Tools. Right now there are no difference between
353
+ // optimization levels greater than zero; they will all invoke the same optimization recipe.
354
+ // https://github.com/Microsoft/DirectXShaderCompiler/blob/main/docs/SPIR-V.rst#optimization
355
+ if (hlslOptions.spirvOptimizer )
356
+ {
357
+ arguments.push_back (L" -O0" );
358
+ }
359
+
360
+ // Debug only values
361
+ if (hlslOptions.genDebugInfo )
362
+ {
363
+ arguments.insert (arguments.end (), {
364
+ DXC_ARG_DEBUG,
365
+ L" -Qembed_debug" ,
366
+ L" -fspv-debug=vulkan-with-source" ,
367
+ L" -fspv-debug=file"
368
+ });
369
+ }
307
370
308
- // const CHLSLCompiler* compiler, nbl::asset::hlsl::impl::DXC* compilerTypes, std::string& source, LPCWSTR* args, uint32_t argCount, const CHLSLCompiler::SOptions& options
309
371
auto compileResult = dxcCompile (
310
372
this ,
311
373
m_dxcCompilerTypes,
312
374
newCode,
313
- &arguments[0 ],
314
- hlslOptions. genDebugInfo ? allArgs : nonDebugArgs,
375
+ &arguments[0 ],
376
+ arguments. size (),
315
377
hlslOptions
316
378
);
317
379
@@ -322,6 +384,11 @@ core::smart_refctd_ptr<ICPUShader> CHLSLCompiler::compileToSPIRV(const char* cod
322
384
323
385
auto outSpirv = core::make_smart_refctd_ptr<ICPUBuffer>(compileResult.objectBlob ->GetBufferSize ());
324
386
memcpy (outSpirv->getPointer (), compileResult.objectBlob ->GetBufferPointer (), compileResult.objectBlob ->GetBufferSize ());
387
+
388
+ // Optimizer step
389
+ if (hlslOptions.spirvOptimizer )
390
+ outSpirv = hlslOptions.spirvOptimizer ->optimize (outSpirv.get (), hlslOptions.preprocessorOptions .logger );
391
+
325
392
326
393
return core::make_smart_refctd_ptr<asset::ICPUShader>(std::move (outSpirv), stage, IShader::E_CONTENT_TYPE::ECT_SPIRV, hlslOptions.preprocessorOptions .sourceIdentifier .data ());
327
394
}
0 commit comments