Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/shaderc
Path: blob/main/libshaderc_util/src/compiler.cc
1560 views
1
// Copyright 2015 The Shaderc Authors. All rights reserved.
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
// http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
15
#include "libshaderc_util/compiler.h"
16
17
#include <cstdint>
18
#include <iomanip>
19
#include <sstream>
20
#include <thread>
21
#include <tuple>
22
23
#include "SPIRV/GlslangToSpv.h"
24
#include "libshaderc_util/format.h"
25
#include "libshaderc_util/io_shaderc.h"
26
#include "libshaderc_util/message.h"
27
#include "libshaderc_util/resources.h"
28
#include "libshaderc_util/shader_stage.h"
29
#include "libshaderc_util/spirv_tools_wrapper.h"
30
#include "libshaderc_util/string_piece.h"
31
#include "libshaderc_util/version_profile.h"
32
#include "spirv-tools/libspirv.hpp"
33
34
namespace {
35
using shaderc_util::string_piece;
36
37
// For use with glslang parsing calls.
38
const bool kNotForwardCompatible = false;
39
40
// Returns true if #line directive sets the line number for the next line in the
41
// given version and profile.
42
inline bool LineDirectiveIsForNextLine(int version, EProfile profile) {
43
return profile == EEsProfile || version >= 330;
44
}
45
46
// Returns a #line directive whose arguments are line and filename.
47
inline std::string GetLineDirective(int line, const string_piece& filename) {
48
return "#line " + std::to_string(line) + " \"" + filename.str() + "\"\n";
49
}
50
51
// Given a canonicalized #line directive (starting exactly with "#line", using
52
// single spaces to separate different components, and having an optional
53
// newline at the end), returns the line number and string name/number. If no
54
// string name/number is provided, the second element in the returned pair is an
55
// empty string_piece. Behavior is undefined if the directive parameter is not a
56
// canonicalized #line directive.
57
std::pair<int, string_piece> DecodeLineDirective(string_piece directive) {
58
const string_piece kLineDirective = "#line ";
59
assert(directive.starts_with(kLineDirective));
60
directive = directive.substr(kLineDirective.size());
61
62
const int line = std::atoi(directive.data());
63
const size_t space_loc = directive.find_first_of(' ');
64
if (space_loc == string_piece::npos) return std::make_pair(line, "");
65
66
directive = directive.substr(space_loc);
67
directive = directive.strip("\" \n");
68
return std::make_pair(line, directive);
69
}
70
71
// Returns the Glslang message rules for the given target environment,
72
// source language, and whether we want HLSL offset rules. We assume
73
// only valid combinations are used.
74
EShMessages GetMessageRules(shaderc_util::Compiler::TargetEnv env,
75
shaderc_util::Compiler::SourceLanguage lang,
76
bool hlsl_offsets, bool hlsl_16bit_types,
77
bool debug_info) {
78
using shaderc_util::Compiler;
79
EShMessages result = EShMsgCascadingErrors;
80
if (lang == Compiler::SourceLanguage::HLSL) {
81
result = static_cast<EShMessages>(result | EShMsgReadHlsl);
82
}
83
switch (env) {
84
case Compiler::TargetEnv::OpenGLCompat:
85
// The compiler will have already errored out before now.
86
// But we need to handle this enum.
87
break;
88
case Compiler::TargetEnv::OpenGL:
89
result = static_cast<EShMessages>(result | EShMsgSpvRules);
90
break;
91
case Compiler::TargetEnv::Vulkan:
92
result =
93
static_cast<EShMessages>(result | EShMsgSpvRules | EShMsgVulkanRules);
94
break;
95
}
96
if (hlsl_offsets) {
97
result = static_cast<EShMessages>(result | EShMsgHlslOffsets);
98
}
99
if (hlsl_16bit_types) {
100
result = static_cast<EShMessages>(result | EShMsgHlslEnable16BitTypes);
101
}
102
if (debug_info) {
103
result = static_cast<EShMessages>(result | EShMsgDebugInfo);
104
}
105
return result;
106
}
107
108
} // anonymous namespace
109
110
namespace shaderc_util {
111
112
unsigned int GlslangInitializer::initialize_count_ = 0;
113
std::mutex* GlslangInitializer::glslang_mutex_ = nullptr;
114
115
GlslangInitializer::GlslangInitializer() {
116
static std::mutex first_call_mutex;
117
118
// If this is the first call, glslang_mutex_ needs to be created, but in
119
// thread safe manner.
120
{
121
const std::lock_guard<std::mutex> first_call_lock(first_call_mutex);
122
if (glslang_mutex_ == nullptr) {
123
glslang_mutex_ = new std::mutex();
124
}
125
}
126
127
const std::lock_guard<std::mutex> glslang_lock(*glslang_mutex_);
128
129
if (initialize_count_ == 0) {
130
glslang::InitializeProcess();
131
}
132
133
initialize_count_++;
134
}
135
136
GlslangInitializer::~GlslangInitializer() {
137
const std::lock_guard<std::mutex> glslang_lock(*glslang_mutex_);
138
139
initialize_count_--;
140
141
if (initialize_count_ == 0) {
142
glslang::FinalizeProcess();
143
// There is no delete for glslang_mutex_ here, because we cannot guarantee
144
// there isn't a caller waiting for glslang_mutex_ in GlslangInitializer().
145
//
146
// This means that this class does leak one std::mutex worth of memory after
147
// the final instance is destroyed, but this allows us to defer allocating
148
// and constructing until we are sure we need to.
149
}
150
}
151
152
void Compiler::SetLimit(Compiler::Limit limit, int value) {
153
switch (limit) {
154
#define RESOURCE(NAME, FIELD, CNAME) \
155
case Limit::NAME: \
156
limits_.FIELD = value; \
157
break;
158
#include "libshaderc_util/resources.inc"
159
#undef RESOURCE
160
}
161
}
162
163
int Compiler::GetLimit(Compiler::Limit limit) const {
164
switch (limit) {
165
#define RESOURCE(NAME, FIELD, CNAME) \
166
case Limit::NAME: \
167
return limits_.FIELD;
168
#include "libshaderc_util/resources.inc"
169
#undef RESOURCE
170
}
171
return 0; // Unreachable
172
}
173
174
std::tuple<bool, std::vector<uint32_t>, size_t> Compiler::Compile(
175
const string_piece& input_source_string, EShLanguage forced_shader_stage,
176
const std::string& error_tag, const char* entry_point_name,
177
const std::function<EShLanguage(std::ostream* error_stream,
178
const string_piece& error_tag)>&
179
stage_callback,
180
CountingIncluder& includer, OutputType output_type,
181
std::ostream* error_stream, size_t* total_warnings, size_t* total_errors) const {
182
// Compilation results to be returned:
183
// Initialize the result tuple as a failed compilation. In error cases, we
184
// should return result_tuple directly without setting its members.
185
auto result_tuple =
186
std::make_tuple(false, std::vector<uint32_t>(), (size_t)0u);
187
// Get the reference of the members of the result tuple. We should set their
188
// values for succeeded compilation before returning the result tuple.
189
bool& succeeded = std::get<0>(result_tuple);
190
std::vector<uint32_t>& compilation_output_data = std::get<1>(result_tuple);
191
size_t& compilation_output_data_size_in_bytes = std::get<2>(result_tuple);
192
193
// Check target environment.
194
const auto target_client_info = GetGlslangClientInfo(
195
error_tag, target_env_, target_env_version_,
196
target_spirv_version_, target_spirv_version_is_forced_);
197
if (!target_client_info.error.empty()) {
198
*error_stream << target_client_info.error;
199
*total_warnings = 0;
200
*total_errors = 1;
201
return result_tuple;
202
}
203
204
EShLanguage used_shader_stage = forced_shader_stage;
205
const std::string macro_definitions =
206
shaderc_util::format(predefined_macros_, "#define ", " ", "\n");
207
const std::string pound_extension =
208
"#extension GL_GOOGLE_include_directive : enable\n";
209
const std::string preamble = macro_definitions + pound_extension;
210
211
std::string preprocessed_shader;
212
213
// If only preprocessing, we definitely need to preprocess. Otherwise, if
214
// we don't know the stage until now, we need the preprocessed shader to
215
// deduce the shader stage.
216
if (output_type == OutputType::PreprocessedText ||
217
used_shader_stage == EShLangCount) {
218
bool success;
219
std::string glslang_errors;
220
std::tie(success, preprocessed_shader, glslang_errors) =
221
PreprocessShader(error_tag, input_source_string, preamble, includer);
222
223
success &= PrintFilteredErrors(error_tag, error_stream, warnings_as_errors_,
224
/* suppress_warnings = */ true,
225
glslang_errors.c_str(), total_warnings,
226
total_errors);
227
if (!success) return result_tuple;
228
// Because of the behavior change of the #line directive, the #line
229
// directive introducing each file's content must use the syntax for the
230
// specified version. So we need to probe this shader's version and
231
// profile.
232
int version;
233
EProfile profile;
234
std::tie(version, profile) = DeduceVersionProfile(preprocessed_shader);
235
const bool is_for_next_line = LineDirectiveIsForNextLine(version, profile);
236
237
preprocessed_shader =
238
CleanupPreamble(preprocessed_shader, error_tag, pound_extension,
239
includer.num_include_directives(), is_for_next_line);
240
241
if (output_type == OutputType::PreprocessedText) {
242
// Set the values of the result tuple.
243
succeeded = true;
244
compilation_output_data = ConvertStringToVector(preprocessed_shader);
245
compilation_output_data_size_in_bytes = preprocessed_shader.size();
246
return result_tuple;
247
} else if (used_shader_stage == EShLangCount) {
248
std::string errors;
249
std::tie(used_shader_stage, errors) =
250
GetShaderStageFromSourceCode(error_tag, preprocessed_shader);
251
if (!errors.empty()) {
252
*error_stream << errors;
253
return result_tuple;
254
}
255
if (used_shader_stage == EShLangCount) {
256
if ((used_shader_stage = stage_callback(error_stream, error_tag)) ==
257
EShLangCount) {
258
return result_tuple;
259
}
260
}
261
}
262
}
263
264
// Parsing requires its own Glslang symbol tables.
265
glslang::TShader shader(used_shader_stage);
266
const char* shader_strings = input_source_string.data();
267
const int shader_lengths = static_cast<int>(input_source_string.size());
268
const char* string_names = error_tag.c_str();
269
shader.setStringsWithLengthsAndNames(&shader_strings, &shader_lengths,
270
&string_names, 1);
271
shader.setPreamble(preamble.c_str());
272
shader.setEntryPoint(entry_point_name);
273
shader.setAutoMapBindings(auto_bind_uniforms_);
274
if (auto_combined_image_sampler_) {
275
shader.setTextureSamplerTransformMode(EShTexSampTransUpgradeTextureRemoveSampler);
276
}
277
shader.setAutoMapLocations(auto_map_locations_);
278
const auto& bases = auto_binding_base_[static_cast<int>(used_shader_stage)];
279
shader.setShiftImageBinding(bases[static_cast<int>(UniformKind::Image)]);
280
shader.setShiftSamplerBinding(bases[static_cast<int>(UniformKind::Sampler)]);
281
shader.setShiftTextureBinding(bases[static_cast<int>(UniformKind::Texture)]);
282
shader.setShiftUboBinding(bases[static_cast<int>(UniformKind::Buffer)]);
283
shader.setShiftSsboBinding(
284
bases[static_cast<int>(UniformKind::StorageBuffer)]);
285
shader.setShiftUavBinding(
286
bases[static_cast<int>(UniformKind::UnorderedAccessView)]);
287
shader.setHlslIoMapping(hlsl_iomap_);
288
shader.setResourceSetBinding(
289
hlsl_explicit_bindings_[static_cast<int>(used_shader_stage)]);
290
shader.setEnvClient(target_client_info.client,
291
target_client_info.client_version);
292
shader.setEnvTarget(target_client_info.target_language,
293
target_client_info.target_language_version);
294
if (hlsl_functionality1_enabled_) {
295
shader.setEnvTargetHlslFunctionality1();
296
}
297
if (vulkan_rules_relaxed_) {
298
glslang::EShSource language = glslang::EShSourceNone;
299
switch(source_language_) {
300
case SourceLanguage::GLSL:
301
language = glslang::EShSourceGlsl;
302
break;
303
case SourceLanguage::HLSL:
304
language = glslang::EShSourceHlsl;
305
break;
306
}
307
// This option will only be used if the Vulkan client is used.
308
// If new versions of GL_KHR_vulkan_glsl come out, it would make sense to
309
// let callers specify which version to use. For now, just use 100.
310
shader.setEnvInput(language, used_shader_stage, glslang::EShClientVulkan, 100);
311
shader.setEnvInputVulkanRulesRelaxed();
312
}
313
shader.setInvertY(invert_y_enabled_);
314
shader.setNanMinMaxClamp(nan_clamp_);
315
316
const EShMessages rules =
317
GetMessageRules(target_env_, source_language_, hlsl_offsets_,
318
hlsl_16bit_types_enabled_, generate_debug_info_);
319
320
bool success = shader.parse(&limits_, default_version_, default_profile_,
321
force_version_profile_, kNotForwardCompatible,
322
rules, includer);
323
324
success &= PrintFilteredErrors(error_tag, error_stream, warnings_as_errors_,
325
suppress_warnings_, shader.getInfoLog(),
326
total_warnings, total_errors);
327
if (!success) return result_tuple;
328
329
glslang::TProgram program;
330
program.addShader(&shader);
331
success = program.link(EShMsgDefault) && program.mapIO();
332
success &= PrintFilteredErrors(error_tag, error_stream, warnings_as_errors_,
333
suppress_warnings_, program.getInfoLog(),
334
total_warnings, total_errors);
335
if (!success) return result_tuple;
336
337
// 'spirv' is an alias for the compilation_output_data. This alias is added
338
// to serve as an input for the call to DissassemblyBinary.
339
std::vector<uint32_t>& spirv = compilation_output_data;
340
glslang::SpvOptions options;
341
options.generateDebugInfo = generate_debug_info_;
342
options.disableOptimizer = true;
343
options.optimizeSize = false;
344
// Note the call to GlslangToSpv also populates compilation_output_data.
345
glslang::GlslangToSpv(*program.getIntermediate(used_shader_stage), spirv,
346
&options);
347
348
// Set the tool field (the top 16-bits) in the generator word to
349
// 'Shaderc over Glslang'.
350
const uint32_t shaderc_generator_word = 13; // From SPIR-V XML Registry
351
const uint32_t generator_word_index = 2; // SPIR-V 2.3: Physical layout
352
assert(spirv.size() > generator_word_index);
353
spirv[generator_word_index] =
354
(spirv[generator_word_index] & 0xffff) | (shaderc_generator_word << 16);
355
356
std::vector<PassId> opt_passes;
357
358
if (hlsl_legalization_enabled_ && source_language_ == SourceLanguage::HLSL) {
359
// If from HLSL, run this passes to "legalize" the SPIR-V for Vulkan
360
// eg. forward and remove memory writes of opaque types.
361
opt_passes.push_back(PassId::kLegalizationPasses);
362
}
363
364
opt_passes.insert(opt_passes.end(), enabled_opt_passes_.begin(),
365
enabled_opt_passes_.end());
366
367
if (!opt_passes.empty()) {
368
spvtools::OptimizerOptions opt_options;
369
opt_options.set_preserve_bindings(preserve_bindings_);
370
371
std::string opt_errors;
372
if (!SpirvToolsOptimize(target_env_, target_env_version_, opt_passes,
373
opt_options, &spirv, &opt_errors)) {
374
*error_stream << "shaderc: internal error: compilation succeeded but "
375
"failed to optimize: "
376
<< opt_errors << "\n";
377
return result_tuple;
378
}
379
}
380
381
if (output_type == OutputType::SpirvAssemblyText) {
382
std::string text_or_error;
383
if (!SpirvToolsDisassemble(target_env_, target_env_version_, spirv,
384
&text_or_error)) {
385
*error_stream << "shaderc: internal error: compilation succeeded but "
386
"failed to disassemble: "
387
<< text_or_error << "\n";
388
return result_tuple;
389
}
390
succeeded = true;
391
compilation_output_data = ConvertStringToVector(text_or_error);
392
compilation_output_data_size_in_bytes = text_or_error.size();
393
return result_tuple;
394
} else {
395
succeeded = true;
396
// Note compilation_output_data is already populated in GlslangToSpv().
397
compilation_output_data_size_in_bytes = spirv.size() * sizeof(spirv[0]);
398
return result_tuple;
399
}
400
}
401
402
void Compiler::AddMacroDefinition(const char* macro, size_t macro_length,
403
const char* definition,
404
size_t definition_length) {
405
predefined_macros_[std::string(macro, macro_length)] =
406
definition ? std::string(definition, definition_length) : "";
407
}
408
409
void Compiler::SetTargetEnv(Compiler::TargetEnv env,
410
Compiler::TargetEnvVersion version) {
411
target_env_ = env;
412
target_env_version_ = version;
413
}
414
415
void Compiler::SetTargetSpirv(Compiler::SpirvVersion version) {
416
target_spirv_version_ = version;
417
target_spirv_version_is_forced_ = true;
418
}
419
420
void Compiler::SetSourceLanguage(Compiler::SourceLanguage lang) {
421
source_language_ = lang;
422
}
423
424
void Compiler::SetForcedVersionProfile(int version, EProfile profile) {
425
default_version_ = version;
426
default_profile_ = profile;
427
force_version_profile_ = true;
428
}
429
430
void Compiler::SetWarningsAsErrors() { warnings_as_errors_ = true; }
431
432
void Compiler::SetGenerateDebugInfo() {
433
generate_debug_info_ = true;
434
for (size_t i = 0; i < enabled_opt_passes_.size(); ++i) {
435
if (enabled_opt_passes_[i] == PassId::kStripDebugInfo) {
436
enabled_opt_passes_[i] = PassId::kNullPass;
437
}
438
}
439
}
440
441
void Compiler::SetOptimizationLevel(Compiler::OptimizationLevel level) {
442
// Clear previous settings first.
443
enabled_opt_passes_.clear();
444
445
switch (level) {
446
case OptimizationLevel::Size:
447
if (!generate_debug_info_) {
448
enabled_opt_passes_.push_back(PassId::kStripDebugInfo);
449
}
450
enabled_opt_passes_.push_back(PassId::kSizePasses);
451
break;
452
case OptimizationLevel::Performance:
453
if (!generate_debug_info_) {
454
enabled_opt_passes_.push_back(PassId::kStripDebugInfo);
455
}
456
enabled_opt_passes_.push_back(PassId::kPerformancePasses);
457
break;
458
default:
459
break;
460
}
461
}
462
463
void Compiler::EnableHlslLegalization(bool hlsl_legalization_enabled) {
464
hlsl_legalization_enabled_ = hlsl_legalization_enabled;
465
}
466
467
void Compiler::EnableHlslFunctionality1(bool enable) {
468
hlsl_functionality1_enabled_ = enable;
469
}
470
471
void Compiler::SetVulkanRulesRelaxed(bool enable) {
472
vulkan_rules_relaxed_ = enable;
473
}
474
475
void Compiler::EnableHlsl16BitTypes(bool enable) {
476
hlsl_16bit_types_enabled_ = enable;
477
}
478
479
void Compiler::EnableInvertY(bool enable) { invert_y_enabled_ = enable; }
480
481
void Compiler::SetNanClamp(bool enable) { nan_clamp_ = enable; }
482
483
void Compiler::SetSuppressWarnings() { suppress_warnings_ = true; }
484
485
std::tuple<bool, std::string, std::string> Compiler::PreprocessShader(
486
const std::string& error_tag, const string_piece& shader_source,
487
const string_piece& shader_preamble, CountingIncluder& includer) const {
488
// The stage does not matter for preprocessing.
489
glslang::TShader shader(EShLangVertex);
490
const char* shader_strings = shader_source.data();
491
const int shader_lengths = static_cast<int>(shader_source.size());
492
const char* string_names = error_tag.c_str();
493
shader.setStringsWithLengthsAndNames(&shader_strings, &shader_lengths,
494
&string_names, 1);
495
shader.setPreamble(shader_preamble.data());
496
auto target_client_info = GetGlslangClientInfo(
497
error_tag, target_env_, target_env_version_,
498
target_spirv_version_, target_spirv_version_is_forced_);
499
if (!target_client_info.error.empty()) {
500
return std::make_tuple(false, "", target_client_info.error);
501
}
502
shader.setEnvClient(target_client_info.client,
503
target_client_info.client_version);
504
if (hlsl_functionality1_enabled_) {
505
shader.setEnvTargetHlslFunctionality1();
506
}
507
shader.setInvertY(invert_y_enabled_);
508
shader.setNanMinMaxClamp(nan_clamp_);
509
510
// The preprocessor might be sensitive to the target environment.
511
// So combine the existing rules with the just-give-me-preprocessor-output
512
// flag.
513
const auto rules = static_cast<EShMessages>(
514
EShMsgOnlyPreprocessor |
515
GetMessageRules(target_env_, source_language_, hlsl_offsets_,
516
hlsl_16bit_types_enabled_, false));
517
518
std::string preprocessed_shader;
519
const bool success = shader.preprocess(
520
&limits_, default_version_, default_profile_, force_version_profile_,
521
kNotForwardCompatible, rules, &preprocessed_shader, includer);
522
523
if (success) {
524
return std::make_tuple(true, preprocessed_shader, shader.getInfoLog());
525
}
526
return std::make_tuple(false, "", shader.getInfoLog());
527
}
528
529
std::string Compiler::CleanupPreamble(const string_piece& preprocessed_shader,
530
const string_piece& error_tag,
531
const string_piece& pound_extension,
532
int num_include_directives,
533
bool is_for_next_line) const {
534
// Those #define directives in preamble will become empty lines after
535
// preprocessing. We also injected an #extension directive to turn on #include
536
// directive support. In the original preprocessing output from glslang, it
537
// appears before the user source string. We need to do proper adjustment:
538
// * Remove empty lines generated from #define directives in preamble.
539
// * If there is no #include directive in the source code, we do not need to
540
// output the injected #extension directive. Otherwise,
541
// * If there exists a #version directive in the source code, it should be
542
// placed at the first line. Its original line will be filled with an empty
543
// line as placeholder to maintain the code structure.
544
545
const std::vector<string_piece> lines =
546
preprocessed_shader.get_fields('\n', /* keep_delimiter = */ true);
547
548
std::ostringstream output_stream;
549
550
size_t pound_extension_index = lines.size();
551
size_t pound_version_index = lines.size();
552
for (size_t i = 0; i < lines.size(); ++i) {
553
if (lines[i] == pound_extension) {
554
pound_extension_index = std::min(i, pound_extension_index);
555
} else if (lines[i].starts_with("#version")) {
556
// In a preprocessed shader, directives are in a canonical format, so we
557
// can confidently compare to '#version' verbatim, without worrying about
558
// whitespace.
559
pound_version_index = i;
560
if (num_include_directives > 0) output_stream << lines[i];
561
break;
562
}
563
}
564
// We know that #extension directive exists and appears before #version
565
// directive (if any).
566
assert(pound_extension_index < lines.size());
567
568
for (size_t i = 0; i < pound_extension_index; ++i) {
569
// All empty lines before the #line directive we injected are generated by
570
// preprocessing preamble. Do not output them.
571
if (lines[i].strip_whitespace().empty()) continue;
572
output_stream << lines[i];
573
}
574
575
if (num_include_directives > 0) {
576
output_stream << pound_extension;
577
// Also output a #line directive for the main file.
578
output_stream << GetLineDirective(is_for_next_line, error_tag);
579
}
580
581
for (size_t i = pound_extension_index + 1; i < lines.size(); ++i) {
582
if (i == pound_version_index) {
583
if (num_include_directives > 0) {
584
output_stream << "\n";
585
} else {
586
output_stream << lines[i];
587
}
588
} else {
589
output_stream << lines[i];
590
}
591
}
592
593
return output_stream.str();
594
}
595
596
std::pair<EShLanguage, std::string> Compiler::GetShaderStageFromSourceCode(
597
string_piece filename, const std::string& preprocessed_shader) const {
598
const string_piece kPragmaShaderStageDirective = "#pragma shader_stage";
599
const string_piece kLineDirective = "#line";
600
601
int version;
602
EProfile profile;
603
std::tie(version, profile) = DeduceVersionProfile(preprocessed_shader);
604
const bool is_for_next_line = LineDirectiveIsForNextLine(version, profile);
605
606
std::vector<string_piece> lines =
607
string_piece(preprocessed_shader).get_fields('\n');
608
// The filename, logical line number (which starts from 1 and is sensitive to
609
// #line directives), and stage value for #pragma shader_stage() directives.
610
std::vector<std::tuple<string_piece, size_t, string_piece>> stages;
611
// The physical line numbers of the first #pragma shader_stage() line and
612
// first non-preprocessing line in the preprocessed shader text.
613
size_t first_pragma_physical_line = lines.size() + 1;
614
size_t first_non_pp_line = lines.size() + 1;
615
616
for (size_t i = 0, logical_line_no = 1; i < lines.size(); ++i) {
617
const string_piece current_line = lines[i].strip_whitespace();
618
if (current_line.starts_with(kPragmaShaderStageDirective)) {
619
const string_piece stage_value =
620
current_line.substr(kPragmaShaderStageDirective.size()).strip("()");
621
stages.emplace_back(filename, logical_line_no, stage_value);
622
first_pragma_physical_line = std::min(first_pragma_physical_line, i + 1);
623
} else if (!current_line.empty() && !current_line.starts_with("#")) {
624
first_non_pp_line = std::min(first_non_pp_line, i + 1);
625
}
626
627
// Update logical line number for the next line.
628
if (current_line.starts_with(kLineDirective)) {
629
string_piece name;
630
std::tie(logical_line_no, name) = DecodeLineDirective(current_line);
631
if (!name.empty()) filename = name;
632
// Note that for core profile, the meaning of #line changed since version
633
// 330. The line number given by #line used to mean the logical line
634
// number of the #line line. Now it means the logical line number of the
635
// next line after the #line line.
636
if (!is_for_next_line) ++logical_line_no;
637
} else {
638
++logical_line_no;
639
}
640
}
641
if (stages.empty()) return std::make_pair(EShLangCount, "");
642
643
std::string error_message;
644
645
const string_piece& first_pragma_filename = std::get<0>(stages[0]);
646
const std::string first_pragma_line = std::to_string(std::get<1>(stages[0]));
647
const string_piece& first_pragma_stage = std::get<2>(stages[0]);
648
649
if (first_pragma_physical_line > first_non_pp_line) {
650
error_message += first_pragma_filename.str() + ":" + first_pragma_line +
651
": error: '#pragma': the first 'shader_stage' #pragma "
652
"must appear before any non-preprocessing code\n";
653
}
654
655
EShLanguage stage = MapStageNameToLanguage(first_pragma_stage);
656
if (stage == EShLangCount) {
657
error_message +=
658
first_pragma_filename.str() + ":" + first_pragma_line +
659
": error: '#pragma': invalid stage for 'shader_stage' #pragma: '" +
660
first_pragma_stage.str() + "'\n";
661
}
662
663
for (size_t i = 1; i < stages.size(); ++i) {
664
const string_piece& current_stage = std::get<2>(stages[i]);
665
if (current_stage != first_pragma_stage) {
666
const string_piece& current_filename = std::get<0>(stages[i]);
667
const std::string current_line = std::to_string(std::get<1>(stages[i]));
668
error_message += current_filename.str() + ":" + current_line +
669
": error: '#pragma': conflicting stages for "
670
"'shader_stage' #pragma: '" +
671
current_stage.str() + "' (was '" +
672
first_pragma_stage.str() + "' at " +
673
first_pragma_filename.str() + ":" + first_pragma_line +
674
")\n";
675
}
676
}
677
678
return std::make_pair(error_message.empty() ? stage : EShLangCount,
679
error_message);
680
}
681
682
std::pair<int, EProfile> Compiler::DeduceVersionProfile(
683
const std::string& preprocessed_shader) const {
684
int version = default_version_;
685
EProfile profile = default_profile_;
686
if (!force_version_profile_) {
687
std::tie(version, profile) =
688
GetVersionProfileFromSourceCode(preprocessed_shader);
689
if (version == 0 && profile == ENoProfile) {
690
version = default_version_;
691
profile = default_profile_;
692
}
693
}
694
return std::make_pair(version, profile);
695
}
696
697
std::pair<int, EProfile> Compiler::GetVersionProfileFromSourceCode(
698
const std::string& preprocessed_shader) const {
699
string_piece pound_version = preprocessed_shader;
700
const size_t pound_version_loc = pound_version.find("#version");
701
if (pound_version_loc == string_piece::npos) {
702
return std::make_pair(0, ENoProfile);
703
}
704
pound_version =
705
pound_version.substr(pound_version_loc + std::strlen("#version"));
706
pound_version = pound_version.substr(0, pound_version.find_first_of("\n"));
707
708
std::string version_profile;
709
for (const auto character : pound_version) {
710
if (character != ' ') version_profile += character;
711
}
712
713
int version;
714
EProfile profile;
715
if (!ParseVersionProfile(version_profile, &version, &profile)) {
716
return std::make_pair(0, ENoProfile);
717
}
718
return std::make_pair(version, profile);
719
}
720
721
// Converts a string to a vector of uint32_t by copying the content of a given
722
// string to a vector<uint32_t> and returns it. Appends '\0' at the end if extra
723
// bytes are required to complete the last element.
724
std::vector<uint32_t> ConvertStringToVector(const std::string& str) {
725
size_t num_bytes_str = str.size() + 1u;
726
size_t vector_length =
727
(num_bytes_str + sizeof(uint32_t) - 1) / sizeof(uint32_t);
728
std::vector<uint32_t> result_vec(vector_length, 0);
729
std::strncpy(reinterpret_cast<char*>(result_vec.data()), str.c_str(),
730
str.size());
731
return result_vec;
732
}
733
734
GlslangClientInfo GetGlslangClientInfo(
735
const std::string& error_tag, shaderc_util::Compiler::TargetEnv env,
736
shaderc_util::Compiler::TargetEnvVersion env_version,
737
shaderc_util::Compiler::SpirvVersion spv_version,
738
bool spv_version_is_forced) {
739
GlslangClientInfo result;
740
std::ostringstream errs;
741
742
using shaderc_util::Compiler;
743
switch (env) {
744
case Compiler::TargetEnv::Vulkan:
745
result.client = glslang::EShClientVulkan;
746
if (env_version == Compiler::TargetEnvVersion::Default ||
747
env_version == Compiler::TargetEnvVersion::Vulkan_1_0) {
748
result.client_version = glslang::EShTargetVulkan_1_0;
749
} else if (env_version == Compiler::TargetEnvVersion::Vulkan_1_1) {
750
result.client_version = glslang::EShTargetVulkan_1_1;
751
result.target_language_version = glslang::EShTargetSpv_1_3;
752
} else if (env_version == Compiler::TargetEnvVersion::Vulkan_1_2) {
753
result.client_version = glslang::EShTargetVulkan_1_2;
754
result.target_language_version = glslang::EShTargetSpv_1_5;
755
} else if (env_version == Compiler::TargetEnvVersion::Vulkan_1_3) {
756
result.client_version = glslang::EShTargetVulkan_1_3;
757
result.target_language_version = glslang::EShTargetSpv_1_6;
758
} else {
759
errs << "error:" << error_tag << ": Invalid target client version "
760
<< static_cast<uint32_t>(env_version) << " for Vulkan environment "
761
<< int(env);
762
}
763
break;
764
case Compiler::TargetEnv::OpenGLCompat:
765
errs << "error: OpenGL compatibility profile is not supported";
766
break;
767
case Compiler::TargetEnv::OpenGL:
768
result.client = glslang::EShClientOpenGL;
769
if (env_version == Compiler::TargetEnvVersion::Default ||
770
env_version == Compiler::TargetEnvVersion::OpenGL_4_5) {
771
result.client_version = glslang::EShTargetOpenGL_450;
772
} else {
773
errs << "error:" << error_tag << ": Invalid target client version "
774
<< static_cast<uint32_t>(env_version) << " for OpenGL environment "
775
<< int(env);
776
}
777
break;
778
default:
779
errs << "error:" << error_tag << ": Invalid target client environment "
780
<< int(env);
781
break;
782
}
783
784
if (spv_version_is_forced && errs.str().empty()) {
785
switch (spv_version) {
786
case Compiler::SpirvVersion::v1_0:
787
result.target_language_version = glslang::EShTargetSpv_1_0;
788
break;
789
case Compiler::SpirvVersion::v1_1:
790
result.target_language_version = glslang::EShTargetSpv_1_1;
791
break;
792
case Compiler::SpirvVersion::v1_2:
793
result.target_language_version = glslang::EShTargetSpv_1_2;
794
break;
795
case Compiler::SpirvVersion::v1_3:
796
result.target_language_version = glslang::EShTargetSpv_1_3;
797
break;
798
case Compiler::SpirvVersion::v1_4:
799
result.target_language_version = glslang::EShTargetSpv_1_4;
800
break;
801
case Compiler::SpirvVersion::v1_5:
802
result.target_language_version = glslang::EShTargetSpv_1_5;
803
break;
804
case Compiler::SpirvVersion::v1_6:
805
result.target_language_version = glslang::EShTargetSpv_1_6;
806
break;
807
default:
808
errs << "error:" << error_tag << ": Unknown SPIR-V version " << std::hex
809
<< uint32_t(spv_version);
810
break;
811
}
812
}
813
result.error = errs.str();
814
return result;
815
}
816
817
} // namespace shaderc_util
818
819