Skip to content

Commit fb58be4

Browse files
authored
Merge pull request #467 from Devsh-Graphics-Programming/hlsl-compiler-change
HLSL compiler updates
2 parents 14cf8e7 + 344ca17 commit fb58be4

File tree

5 files changed

+138
-44
lines changed

5 files changed

+138
-44
lines changed

3rdparty/tcpp

include/nbl/asset/utils/IShaderCompiler.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ namespace nbl::asset
2020
class NBL_API2 IShaderCompiler : public core::IReferenceCounted
2121
{
2222
public:
23+
//string to be replaced with all "#" except those in "#include"
24+
static constexpr const char* PREPROC_DIRECTIVE_DISABLER = "_this_is_a_hash_";
25+
static constexpr const char* PREPROC_DIRECTIVE_ENABLER = PREPROC_DIRECTIVE_DISABLER;
2326

2427
class NBL_API2 IIncludeLoader : public core::IReferenceCounted
2528
{
@@ -254,12 +257,16 @@ class NBL_API2 IShaderCompiler : public core::IReferenceCounted
254257
}
255258
}
256259

260+
static std::string escapeFilename(std::string&& code);
261+
257262
static void disableAllDirectivesExceptIncludes(std::string& _code);
258263

259264
static void reenableDirectives(std::string& _code);
260265

261266
static std::string encloseWithinExtraInclGuards(std::string&& _code, uint32_t _maxInclusions, const char* _identifier);
262267

268+
static uint32_t encloseWithinExtraInclGuardsLeadingLines(uint32_t _maxInclusions);
269+
263270
virtual IShader::E_CONTENT_TYPE getCodeContentType() const = 0;
264271

265272
CIncludeFinder* getDefaultIncludeFinder() { return m_defaultIncludeFinder.get(); }

src/nbl/asset/utils/CCompilerSet.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ core::smart_refctd_ptr<ICPUShader> CCompilerSet::preprocessShader(const ICPUShad
5757
return core::make_smart_refctd_ptr<ICPUShader>(resolvedCode.c_str(), stage, IShader::E_CONTENT_TYPE::ECT_GLSL, std::string(shader->getFilepathHint()));
5858
}
5959
break;
60+
case IShader::E_CONTENT_TYPE::ECT_SPIRV:
61+
return core::smart_refctd_ptr<ICPUShader>(const_cast<ICPUShader*>(shader));
6062
default:
6163
return nullptr;
6264
}

src/nbl/asset/utils/CHLSLCompiler.cpp

Lines changed: 100 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ using namespace nbl;
2121
using namespace nbl::asset;
2222
using Microsoft::WRL::ComPtr;
2323

24+
static constexpr const wchar_t* SHADER_MODEL_PROFILE = L"XX_6_6";
25+
2426
namespace nbl::asset::hlsl::impl
2527
{
2628
struct DXC {
@@ -52,43 +54,61 @@ CHLSLCompiler::~CHLSLCompiler()
5254
}
5355

5456
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
6166
)
6267
{
6368
std::string res_str;
6469

6570
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);
6873
if (!reqFromBuiltin && !reqBuiltin)
6974
{
7075
//While #includ'ing a builtin, one must specify its full path (starting with "nbl/builtin" or "/nbl/builtin").
7176
// This rule applies also while a builtin is #includ`ing another builtin.
7277
//While including a filesystem file it must be either absolute path (or relative to any search dir added to asset::iIncludeHandler; <>-type),
7378
// 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();
7580
}
76-
std::filesystem::path name = _type ? (relDir / _requested_source) : (_requested_source);
81+
std::filesystem::path name = isRelative ? (relDir / requestedSource) : (requestedSource);
7782

7883
if (std::filesystem::exists(name) && !reqBuiltin)
7984
name = std::filesystem::absolute(name);
8085

81-
if (_type)
82-
res_str = _inclFinder->getIncludeRelative(relDir, _requested_source);
86+
if (isRelative)
87+
res_str = inclFinder->getIncludeRelative(relDir, requestedSource);
8388
else //shaderc_include_type_standard
84-
res_str = _inclFinder->getIncludeStandard(relDir, _requested_source);
89+
res_str = inclFinder->getIncludeStandard(relDir, requestedSource);
8590

8691
if (!res_str.size()) {
8792
return new tcpp::StringInputStream("#error File not found");
8893
}
8994

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+
90103
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())));
92112

93113
return new tcpp::StringInputStream(std::move(res_str));
94114
}
@@ -100,9 +120,9 @@ class DxcCompilationResult
100120
ComPtr<IDxcBlob> objectBlob;
101121
ComPtr<IDxcResult> compileResult;
102122

103-
char* GetErrorMessagesString()
123+
std::string GetErrorMessagesString()
104124
{
105-
return reinterpret_cast<char*>(errorMessages->GetBufferPointer());
125+
return std::string(reinterpret_cast<char*>(errorMessages->GetBufferPointer()), errorMessages->GetBufferSize());
106126
}
107127
};
108128

@@ -151,9 +171,17 @@ DxcCompilationResult dxcCompile(const CHLSLCompiler* compiler, nbl::asset::hlsl:
151171
result.compileResult = compileResult;
152172
result.objectBlob = nullptr;
153173

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
155183
{
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());
157185
return result;
158186
}
159187

@@ -169,13 +197,22 @@ DxcCompilationResult dxcCompile(const CHLSLCompiler* compiler, nbl::asset::hlsl:
169197

170198
std::string CHLSLCompiler::preprocessShader(std::string&& code, IShader::E_SHADER_STAGE& stage, const SPreprocessorOptions& preprocessOptions) const
171199
{
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);
172205
if (preprocessOptions.extraDefines.size())
173206
{
174207
insertExtraDefines(code, preprocessOptions.extraDefines);
208+
defineLeadingLinesMain += preprocessOptions.extraDefines.size();
175209
}
176210

177211
IShaderCompiler::disableAllDirectivesExceptIncludes(code);
178212

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+
179216
tcpp::StringInputStream codeIs = tcpp::StringInputStream(code);
180217
tcpp::Lexer lexer(codeIs);
181218
tcpp::Preprocessor proc(
@@ -188,13 +225,17 @@ std::string CHLSLCompiler::preprocessShader(std::string&& code, IShader::E_SHADE
188225
{
189226
return getInputStreamInclude(
190227
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
192230
);
193231
}
194232
else
195233
{
196234
return static_cast<tcpp::IInputStream*>(new tcpp::StringInputStream(std::string("#error No include handler")));
197235
}
236+
},
237+
[&]() {
238+
lineOffsetStack.pop_back();
198239
}
199240
);
200241

@@ -239,6 +280,7 @@ std::string CHLSLCompiler::preprocessShader(std::string&& code, IShader::E_SHADE
239280

240281
auto resolvedString = proc.Process();
241282
IShaderCompiler::reenableDirectives(resolvedString);
283+
242284
return resolvedString;
243285
}
244286

@@ -256,7 +298,16 @@ core::smart_refctd_ptr<ICPUShader> CHLSLCompiler::compileToSPIRV(const char* cod
256298
auto newCode = preprocessShader(code, stage, hlslOptions.preprocessorOptions);
257299

258300
// 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);
260311

261312
// Set profile two letter prefix based on stage
262313
switch (stage) {
@@ -289,29 +340,40 @@ core::smart_refctd_ptr<ICPUShader> CHLSLCompiler::compileToSPIRV(const char* cod
289340
return nullptr;
290341
};
291342

292-
LPCWSTR arguments[] = {
293-
// These will always be present
343+
std::vector<LPCWSTR> arguments = {
294344
L"-spirv",
295345
L"-HV", L"2021",
296346
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"
303347
};
304348

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+
}
307370

308-
// const CHLSLCompiler* compiler, nbl::asset::hlsl::impl::DXC* compilerTypes, std::string& source, LPCWSTR* args, uint32_t argCount, const CHLSLCompiler::SOptions& options
309371
auto compileResult = dxcCompile(
310372
this,
311373
m_dxcCompilerTypes,
312374
newCode,
313-
&arguments[0],
314-
hlslOptions.genDebugInfo ? allArgs : nonDebugArgs,
375+
&arguments[0],
376+
arguments.size(),
315377
hlslOptions
316378
);
317379

@@ -322,6 +384,11 @@ core::smart_refctd_ptr<ICPUShader> CHLSLCompiler::compileToSPIRV(const char* cod
322384

323385
auto outSpirv = core::make_smart_refctd_ptr<ICPUBuffer>(compileResult.objectBlob->GetBufferSize());
324386
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+
325392

326393
return core::make_smart_refctd_ptr<asset::ICPUShader>(std::move(outSpirv), stage, IShader::E_CONTENT_TYPE::ECT_SPIRV, hlslOptions.preprocessorOptions.sourceIdentifier.data());
327394
}

src/nbl/asset/utils/IShaderCompiler.cpp

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,32 @@
1212
using namespace nbl;
1313
using namespace nbl::asset;
1414

15-
//string to be replaced with all "#" except those in "#include"
16-
static constexpr const char* PREPROC_DIRECTIVE_DISABLER = "_this_is_a_hash_";
17-
static constexpr const char* PREPROC_DIRECTIVE_ENABLER = PREPROC_DIRECTIVE_DISABLER;
15+
std::string IShaderCompiler::escapeFilename(std::string&& code)
16+
{
17+
std::string dest;
18+
dest.reserve(code.size() * 2);
19+
for (char c : code)
20+
{
21+
if (c == '\\')
22+
dest.append("\\" "\\");
23+
else
24+
dest.push_back(c);
25+
}
26+
return dest;
27+
}
1828

1929
//all "#", except those in "#include"/"#version"/"#pragma shader_stage(...)", replaced with `PREPROC_DIRECTIVE_DISABLER`
2030
void IShaderCompiler::disableAllDirectivesExceptIncludes(std::string& _code)
2131
{
2232
// TODO: replace this with a proper-ish proprocessor and includer one day
2333
std::regex directive("#(?!(include|version|pragma shader_stage))");//all # not followed by "include" nor "version" nor "pragma shader_stage"
2434
//`#pragma shader_stage(...)` is needed for determining shader stage when `_stage` param of IShaderCompiler functions is set to ESS_UNKNOWN
25-
_code = std::regex_replace(_code, directive, PREPROC_DIRECTIVE_DISABLER);
35+
_code = std::regex_replace(_code, directive, IShaderCompiler::PREPROC_DIRECTIVE_DISABLER);
2636
}
2737

2838
void IShaderCompiler::reenableDirectives(std::string& _code)
2939
{
30-
std::regex directive(PREPROC_DIRECTIVE_ENABLER);
40+
std::regex directive(IShaderCompiler::PREPROC_DIRECTIVE_ENABLER);
3141
_code = std::regex_replace(_code, directive, "#");
3242
}
3343

@@ -60,24 +70,32 @@ std::string IShaderCompiler::encloseWithinExtraInclGuards(std::string&& _code, u
6070
return undefs;
6171
};
6272

63-
// HACK: tcpp is having issues parsing the string, so this is a hack/mitigation that could be removed once tcpp is fixed
64-
std::string identifier = _identifier;
65-
std::replace(identifier.begin(), identifier.end(), '\\', '/');
66-
73+
std::string identifier = IShaderCompiler::escapeFilename(_identifier);
6774
return
6875
genDefs() +
6976
"\n"
7077
"#ifndef " + defBase_ + std::to_string(_maxInclusions) +
7178
"\n" +
7279
// This will get turned back into #line after the directives get re-enabled
73-
PREPROC_DIRECTIVE_DISABLER + "line 1 \"" + identifier.c_str() + "\"\n" +
80+
IShaderCompiler::PREPROC_DIRECTIVE_DISABLER + "line 1 \"" + identifier.c_str() + "\"\n" +
7481
_code +
7582
"\n"
7683
"#endif"
7784
"\n\n" +
7885
genUndefs();
7986
}
8087

88+
// Amount of lines before the #line after having run encloseWithinExtraInclGuards
89+
uint32_t IShaderCompiler::encloseWithinExtraInclGuardsLeadingLines(uint32_t _maxInclusions)
90+
{
91+
auto lineDirectiveString = std::string(IShaderCompiler::PREPROC_DIRECTIVE_DISABLER) + "line";
92+
std::string str = IShaderCompiler::encloseWithinExtraInclGuards(std::string(""), _maxInclusions, "encloseWithinExtraInclGuardsLeadingLines");
93+
size_t lineDirectivePos = str.find(lineDirectiveString);
94+
auto substr = str.substr(0, lineDirectivePos - lineDirectiveString.length());
95+
96+
return std::count(substr.begin(), substr.end(), '\n');
97+
}
98+
8199
IShaderCompiler::IShaderCompiler(core::smart_refctd_ptr<system::ISystem>&& system)
82100
: m_system(std::move(system))
83101
{

0 commit comments

Comments
 (0)