yum-mirror/slang

Making it easier to work with shaders

git clone https://git.yummers.dev/yum-mirror/slang

Jeremy HayesUse glslang public API (#8369)134c4c8db

master
36.6 KiB1080 linesraw
1// slang-glslang.cpp
2#include "slang-glslang.h"
3
4#include "SPIRV/GlslangToSpv.h"
5#include "glslang/Public/ShaderLang.h"
6#include "slang.h"
7#include "spirv-tools/libspirv.h"
8#include "spirv-tools/linker.hpp"
9#include "spirv-tools/optimizer.hpp"
10
11#ifdef _WIN32
12#include <windows.h>
13#endif
14
15#include <cassert>
16#include <iostream>
17#include <memory>
18#include <mutex>
19#include <sstream>
20
21// This is a wrapper to allow us to run the `glslang` compiler
22// in a controlled fashion.
23
24#define UNLIMITED 9999
25
26static TBuiltInResource _calcBuiltinResources()
27{
28    // NOTE! This is a bit of a hack - to set all the fields to true/UNLIMITED.
29    // Care must be taken if new variables are introduced, the default may not be appropriate.
30
31    // We are relying on limits being after the other fields.
32    SLANG_COMPILE_TIME_ASSERT(SLANG_OFFSET_OF(TBuiltInResource, limits) > 0);
33    // We are relying on maxLights being the first parameter, and all values will have the same type
34    SLANG_COMPILE_TIME_ASSERT(SLANG_OFFSET_OF(TBuiltInResource, maxLights) == 0);
35
36    TBuiltInResource resource;
37    // Set up all the integer values.
38    {
39
40        auto* dst = &resource.maxLights;
41        const size_t count = SLANG_OFFSET_OF(TBuiltInResource, limits) / sizeof(*dst);
42        for (size_t i = 0; i < count; ++i)
43        {
44            dst[i] = UNLIMITED;
45        }
46    }
47
48    // In the sea of variables there is a min value
49    resource.minProgramTexelOffset = -UNLIMITED;
50
51    // Set up the bools
52    {
53        TLimits* limits = &resource.limits;
54        bool* dst = (bool*)limits;
55
56        const size_t count = sizeof(TLimits) / sizeof(bool);
57        for (size_t i = 0; i < count; ++i)
58        {
59            dst[i] = true;
60        }
61    }
62    return resource;
63}
64
65static TBuiltInResource gResources = _calcBuiltinResources();
66
67static void dump(
68    void const* data,
69    size_t size,
70    glslang_OutputFunc outputFunc,
71    void* outputUserData,
72    FILE* fallbackStream)
73{
74    if (outputFunc)
75    {
76        outputFunc(data, size, outputUserData);
77    }
78    else
79    {
80        fwrite(data, 1, size, fallbackStream);
81
82        // also output it for debug purposes
83        std::string str((char const*)data, size);
84#ifdef _WIN32
85        OutputDebugStringA(str.c_str());
86#else
87        fprintf(stderr, "%s\n", str.c_str());
88        ;
89#endif
90    }
91}
92
93static void dumpDiagnostics(const glslang_CompileRequest_1_2& request, std::string const& log)
94{
95    dump(log.c_str(), log.length(), request.diagnosticFunc, request.diagnosticUserData, stderr);
96}
97
98struct SPIRVOptimizationDiagnostic
99{
100    std::string toString() const
101    {
102        std::ostringstream out;
103
104        switch (level)
105        {
106        case SPV_MSG_FATAL:
107        case SPV_MSG_INTERNAL_ERROR:
108        case SPV_MSG_ERROR:
109            out << "error: ";
110            break;
111        case SPV_MSG_WARNING:
112            out << "warning: ";
113            break;
114        case SPV_MSG_INFO:
115        case SPV_MSG_DEBUG:
116            out << "info: ";
117            break;
118        default:
119            break;
120        }
121        if (source.length())
122        {
123            out << source << ":";
124        }
125        out << position.line << ":" << position.column << ":" << position.index << ":";
126        if (message.length())
127        {
128            out << " " << message;
129        }
130
131        return out.str();
132    }
133
134    spv_message_level_t level;
135    std::string source;
136    spv_position_t position;
137    std::string message;
138};
139
140// TODO: the actual printing should happen on the application side.
141static void validationMessageConsumer(
142    spv_message_level_t level,
143    const char*,
144    const spv_position_t& position,
145    const char* message)
146{
147    switch (level)
148    {
149    case SPV_MSG_FATAL:
150    case SPV_MSG_INTERNAL_ERROR:
151    case SPV_MSG_ERROR:
152        std::cerr << "error: line " << position.index << ": " << message << std::endl;
153        break;
154    case SPV_MSG_WARNING:
155        std::cout << "warning: line " << position.index << ": " << message << std::endl;
156        break;
157    case SPV_MSG_INFO:
158        std::cout << "info: line " << position.index << ": " << message << std::endl;
159        break;
160    default:
161        break;
162    }
163}
164
165// Validate the given SPIRV-ASM instructions.
166extern "C"
167#ifdef _MSC_VER
168    _declspec(dllexport)
169#else
170    __attribute__((__visibility__("default")))
171#endif
172        bool glslang_validateSPIRV(const uint32_t* contents, int contentsSize)
173{
174    spv_target_env target_env = SPV_ENV_UNIVERSAL_1_6;
175
176    spvtools::ValidatorOptions options;
177    options.SetScalarBlockLayout(true);
178    options.SetFriendlyNames(true);
179
180    spvtools::SpirvTools tools(target_env);
181    tools.SetMessageConsumer(validationMessageConsumer);
182
183    return tools.Validate(contents, contentsSize, options);
184}
185
186// Disassemble the given SPIRV-ASM instructions and return the result as a string.
187extern "C"
188#ifdef _MSC_VER
189    _declspec(dllexport)
190#else
191    __attribute__((__visibility__("default")))
192#endif
193        bool glslang_disassembleSPIRVWithResult(
194            const uint32_t* contents,
195            int contentsSize,
196            char** outString)
197{
198    static const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_5;
199    spv_text text;
200
201    uint32_t options = SPV_BINARY_TO_TEXT_OPTION_NONE;
202    options |= SPV_BINARY_TO_TEXT_OPTION_COMMENT;
203    options |= SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES;
204    options |= SPV_BINARY_TO_TEXT_OPTION_INDENT;
205
206    spv_diagnostic diagnostic = nullptr;
207    spv_context context = spvContextCreate(kDefaultEnvironment);
208    spv_result_t error =
209        spvBinaryToText(context, contents, contentsSize, options, &text, &diagnostic);
210    spvContextDestroy(context);
211    if (error)
212    {
213        spvDiagnosticPrint(diagnostic);
214        spvDiagnosticDestroy(diagnostic);
215        return false;
216    }
217    else
218    {
219        if (outString)
220        {
221            // Allocate memory for the output string and copy the result
222            size_t len = text->length + 1; // +1 for null terminator
223            *outString = new char[len];
224            memcpy(*outString, text->str, text->length);
225            (*outString)[text->length] = '\0'; // Ensure null termination
226        }
227
228        spvTextDestroy(text);
229        return true;
230    }
231}
232
233
234// Disassemble the given SPIRV-ASM instructions.
235extern "C"
236#ifdef _MSC_VER
237    _declspec(dllexport)
238#else
239    __attribute__((__visibility__("default")))
240#endif
241        bool glslang_disassembleSPIRV(const uint32_t* contents, int contentsSize)
242{
243    char* result = nullptr;
244    auto succ = glslang_disassembleSPIRVWithResult(contents, contentsSize, &result);
245    if (result)
246        fprintf(stdout, "%s\n", result);
247    delete result;
248    return succ;
249}
250
251// Apply the SPIRV-Tools optimizer to generated SPIR-V based on the desired optimization level
252// TODO: add flag for optimizing SPIR-V size as well
253static void glslang_optimizeSPIRV(
254    spv_target_env targetEnv,
255    const glslang_CompileRequest_1_2& request,
256    std::vector<SPIRVOptimizationDiagnostic>& outDiags,
257    std::vector<unsigned int>& ioSpirv)
258{
259    const auto optimizationLevel = request.optimizationLevel;
260
261    // If there is no optimization then we are done
262    if (optimizationLevel == SLANG_OPTIMIZATION_LEVEL_NONE)
263    {
264        return;
265    }
266
267    const auto debugInfoType = request.debugInfoType;
268
269    spvtools::Optimizer optimizer(targetEnv);
270
271    optimizer.SetMessageConsumer(
272        [&](spv_message_level_t level,
273            const char* source,
274            const spv_position_t& position,
275            const char* message)
276        {
277            SPIRVOptimizationDiagnostic diag;
278            diag.level = level;
279            if (source)
280            {
281                diag.source = source;
282            }
283            diag.position = position;
284            if (message)
285            {
286                diag.message = message;
287            }
288            outDiags.push_back(diag);
289        });
290
291    // If debug info is being generated, propagate
292    // line information into all SPIR-V instructions. This avoids loss of
293    // information when instructions are deleted or moved. Later, remove
294    // redundant information to minimize final SPRIR-V size.
295    if (debugInfoType != SLANG_DEBUG_INFO_LEVEL_NONE)
296    {
297        optimizer.RegisterPass(spvtools::CreatePropagateLineInfoPass());
298    }
299
300    spvtools::OptimizerOptions spvOptOptions;
301
302    // To compile some large shaders the default is not enough.
303    // That although this limit is exceeded, the final optimized output is typically well
304    // within the range.
305    //
306    // See kDefaultMaxIdBound for description of this limit.
307    //
308    // If a compilation produces a warning like
309    // `0:0: ID overflow. Try running compact-ids.`
310    // it might be fixable by raising the multiplier to a larger value.
311    spvOptOptions.set_max_id_bound(kDefaultMaxIdBound * 4);
312
313    // TODO confirm which passes we want to invoke for each level
314    switch (optimizationLevel)
315    {
316    default:
317    case SLANG_OPTIMIZATION_LEVEL_DEFAULT:
318        {
319            // Use a minimal set of performance settings
320            // If we run CreateInlineExhaustivePass, We need to run CreateMergeReturnPass first.
321
322#if 0
323            // This is the previous 'default optimization' passes setting for glslang
324            optimizer.RegisterPass(spvtools::CreateMergeReturnPass());
325            optimizer.RegisterPass(spvtools::CreateInlineExhaustivePass());
326            optimizer.RegisterPass(spvtools::CreateAggressiveDCEPass());
327            optimizer.RegisterPass(spvtools::CreatePrivateToLocalPass());
328            optimizer.RegisterPass(spvtools::CreateScalarReplacementPass(100));
329            optimizer.RegisterPass(spvtools::CreateLocalAccessChainConvertPass());
330            optimizer.RegisterPass(spvtools::CreateAggressiveDCEPass());
331#elif 1
332            // 6Mb 27 secs (all passes up to 9)
333            // 9Mb 25 secs (all passes up to 7)
334            // 8Mb 15 secs (all passes) -(5,6,7)
335            // 6Mb 15 secs (all passes) -(6,7)
336
337            // This list of passes takes the previous 'default optimization'
338            // passes (as listed above) and tries to combine them in order with the 'new' passes
339            // below. The issue with the passes below is that although it produces smaller SPIR-V
340            // fairly quickly it can cause serious problem on some drivers.
341            //
342            // Across a wide range of compilations this produced SPIR-V that is less than half size
343            // of the previous -O1 passes above.
344
345            optimizer.RegisterPass(spvtools::CreateWrapOpKillPass());     // 1
346            optimizer.RegisterPass(spvtools::CreateDeadBranchElimPass()); // 2
347
348            optimizer.RegisterPass(spvtools::CreateMergeReturnPass());
349            optimizer.RegisterPass(spvtools::CreateInlineExhaustivePass());
350
351            optimizer.RegisterPass(spvtools::CreateEliminateDeadFunctionsPass()); // 3
352
353            optimizer.RegisterPass(spvtools::CreateAggressiveDCEPass());
354            optimizer.RegisterPass(spvtools::CreatePrivateToLocalPass());
355
356            optimizer.RegisterPass(spvtools::CreateScalarReplacementPass(100));
357
358            optimizer.RegisterPass(spvtools::CreateCCPPass());            // 4 *
359            optimizer.RegisterPass(spvtools::CreateSimplificationPass()); // 5
360            // optimizer.RegisterPass(spvtools::CreateIfConversionPass());         // 6
361            // optimizer.RegisterPass(spvtools::CreateBlockMergePass());           // 7 *
362
363            optimizer.RegisterPass(spvtools::CreateLocalAccessChainConvertPass());
364
365            optimizer.RegisterPass(spvtools::CreateLocalSingleBlockLoadStoreElimPass()); // 8
366
367            optimizer.RegisterPass(spvtools::CreateAggressiveDCEPass());
368
369            optimizer.RegisterPass(spvtools::CreateVectorDCEPass()); // 9
370
371#else
372            // The following selection of passes was created by
373            // 1) Taking the list of passes from optimizer.RegisterSizePasses
374            // 2) Disable/enable passes to try to produce some reasonable combination of low SPIR-V
375            // output size and compilation speed
376            //
377            // For a particularly difficult glsl shader this produced 1/3 SPIR-V code (against
378            // previous -O1), in around 13th the time (against -O3 option) Over a wide range of
379            // compiles the SPIR-V is around 6% larger than -O3
380
381            // The following comments describe the path to finding this combination. The original
382            // compilation produces 18Mb SPIR-V binaries in around 3 1/2 mins. The integer number
383            // increases with the ordering of the test.
384            //
385            // With 5 47s
386            // With 6 we have 6Mb, and 38 seconds
387            // With 7 we have 6Mb and 26 seconds
388            // With 8 we have 6Mb in 18 seconds
389            // 9 didn't improve perf or size
390            // With 10 we have 6Mb in 16.8
391            // With 11 we have 6Mb in 16.1
392            // With 12 we have 6Mb in 15.6
393            // With 13 didn't improve
394            // With 14 slightly larger, slightly smaller, so leave
395            // Try 15 - Adding one and removing the other, makes things much worse
396            // Without any SSA rewrite we are up to 6Mb. 48
397            //
398            // So (for test case) approximately 13x compilation speed.
399            // Binary twice the size of smallest SPIR-V size and 1/3 the size of the previous -O
400            // size
401            optimizer.RegisterPass(spvtools::CreateWrapOpKillPass());
402            optimizer.RegisterPass(spvtools::CreateDeadBranchElimPass()); // 15
403            optimizer.RegisterPass(spvtools::CreateMergeReturnPass());
404            optimizer.RegisterPass(spvtools::CreateInlineExhaustivePass());
405            optimizer.RegisterPass(spvtools::CreateEliminateDeadFunctionsPass()); // 9
406            optimizer.RegisterPass(spvtools::CreatePrivateToLocalPass());
407            // optimizer.RegisterPass(spvtools::CreateScalarReplacementPass(0));   // 12
408            // optimizer.RegisterPass(spvtools::CreateLocalMultiStoreElimPass());
409            optimizer.RegisterPass(spvtools::CreateCCPPass());
410            // optimizer.RegisterPass(spvtools::CreateLoopUnrollPass(true));     // 1
411            // optimizer.RegisterPass(spvtools::CreateDeadBranchElimPass());     // 4
412            // optimizer.RegisterPass(spvtools::CreateSimplificationPass());       // 11
413            optimizer.RegisterPass(spvtools::CreateScalarReplacementPass(0));
414            // optimizer.RegisterPass(spvtools::CreateLocalSingleStoreElimPass());
415            // optimizer.RegisterPass(spvtools::CreateIfConversionPass());       // 7
416            optimizer.RegisterPass(spvtools::CreateSimplificationPass()); // 13
417            // optimizer.RegisterPass(spvtools::CreateAggressiveDCEPass());      // 10
418            // optimizer.RegisterPass(spvtools::CreateDeadBranchElimPass());         // 6 + 15
419            // optimizer.RegisterPass(spvtools::CreateBlockMergePass());             // 8
420            optimizer.RegisterPass(spvtools::CreateLocalAccessChainConvertPass());
421            optimizer.RegisterPass(spvtools::CreateLocalSingleBlockLoadStoreElimPass());
422            optimizer.RegisterPass(spvtools::CreateAggressiveDCEPass()); // 5
423            // optimizer.RegisterPass(spvtools::CreateCopyPropagateArraysPass());          // 1
424            optimizer.RegisterPass(spvtools::CreateVectorDCEPass());
425            optimizer.RegisterPass(spvtools::CreateDeadInsertElimPass());
426            optimizer.RegisterPass(spvtools::CreateEliminateDeadMembersPass());
427            // optimizer.RegisterPass(spvtools::CreateLocalSingleStoreElimPass());
428            // optimizer.RegisterPass(spvtools::CreateBlockMergePass());                 // 3
429            // optimizer.RegisterPass(spvtools::CreateLocalMultiStoreElimPass());        // 2
430            // optimizer.RegisterPass(spvtools::CreateRedundancyEliminationPass());
431            optimizer.RegisterPass(spvtools::CreateSimplificationPass()); // 14
432            optimizer.RegisterPass(spvtools::CreateAggressiveDCEPass());
433            optimizer.RegisterPass(spvtools::CreateCFGCleanupPass());
434#endif
435
436            break;
437        }
438    // TODO(JS): It would be better if we had some distinction here where 'high' meant optimize
439    // 'in a reasonable time' for a better optimization, and 'maximal' meant compilation might
440    // take a really long time... so only use it if it's really needed.
441    //
442    // Currently we just have high have the same meaning as 'maximal'.
443    case SLANG_OPTIMIZATION_LEVEL_HIGH:
444    case SLANG_OPTIMIZATION_LEVEL_MAXIMAL:
445        {
446            // Use the same passes when specifying the "-O" flag in spirv-opt
447            // Roughly equivalent to `RegisterPerformancePasses`
448
449            optimizer.RegisterPass(spvtools::CreateWrapOpKillPass());
450            optimizer.RegisterPass(spvtools::CreateDeadBranchElimPass());
451            optimizer.RegisterPass(spvtools::CreateMergeReturnPass());
452            optimizer.RegisterPass(spvtools::CreateInlineExhaustivePass());
453            optimizer.RegisterPass(spvtools::CreateEliminateDeadFunctionsPass());
454            optimizer.RegisterPass(spvtools::CreateAggressiveDCEPass());
455            optimizer.RegisterPass(spvtools::CreatePrivateToLocalPass());
456            optimizer.RegisterPass(spvtools::CreateLocalSingleBlockLoadStoreElimPass());
457            optimizer.RegisterPass(spvtools::CreateLocalSingleStoreElimPass());
458            optimizer.RegisterPass(spvtools::CreateAggressiveDCEPass());
459            optimizer.RegisterPass(spvtools::CreateScalarReplacementPass());
460            optimizer.RegisterPass(spvtools::CreateLocalAccessChainConvertPass());
461            optimizer.RegisterPass(spvtools::CreateLocalSingleBlockLoadStoreElimPass());
462            optimizer.RegisterPass(spvtools::CreateLocalSingleStoreElimPass());
463            optimizer.RegisterPass(spvtools::CreateAggressiveDCEPass());
464
465            // We run CompactIdsPass here, because CreateLocalMultiStoreElimPass can explode
466            // id usage (by a factor of 10), and compacting ids here has been shown to half
467            // id usage with a complex shader.
468            optimizer.RegisterPass(spvtools::CreateCompactIdsPass());
469
470            // Note that CreateLocalMultiStoreElimPass really just does a SSARewritePass
471            optimizer.RegisterPass(spvtools::CreateLocalMultiStoreElimPass());
472
473            optimizer.RegisterPass(spvtools::CreateAggressiveDCEPass());
474            optimizer.RegisterPass(spvtools::CreateCCPPass());
475            optimizer.RegisterPass(spvtools::CreateAggressiveDCEPass());
476            optimizer.RegisterPass(spvtools::CreateLoopUnrollPass(true));
477            optimizer.RegisterPass(spvtools::CreateDeadBranchElimPass());
478            optimizer.RegisterPass(spvtools::CreateRedundancyEliminationPass());
479            optimizer.RegisterPass(spvtools::CreateCombineAccessChainsPass());
480            optimizer.RegisterPass(spvtools::CreateSimplificationPass());
481            optimizer.RegisterPass(spvtools::CreateScalarReplacementPass());
482            optimizer.RegisterPass(spvtools::CreateLocalAccessChainConvertPass());
483            optimizer.RegisterPass(spvtools::CreateLocalSingleBlockLoadStoreElimPass());
484            optimizer.RegisterPass(spvtools::CreateLocalSingleStoreElimPass());
485            optimizer.RegisterPass(spvtools::CreateAggressiveDCEPass());
486            optimizer.RegisterPass(spvtools::CreateSSARewritePass());
487            optimizer.RegisterPass(spvtools::CreateAggressiveDCEPass());
488            optimizer.RegisterPass(spvtools::CreateVectorDCEPass());
489            optimizer.RegisterPass(spvtools::CreateDeadInsertElimPass());
490            optimizer.RegisterPass(spvtools::CreateDeadBranchElimPass());
491            optimizer.RegisterPass(spvtools::CreateSimplificationPass());
492            optimizer.RegisterPass(spvtools::CreateIfConversionPass());
493            optimizer.RegisterPass(spvtools::CreateCopyPropagateArraysPass());
494            optimizer.RegisterPass(spvtools::CreateReduceLoadSizePass());
495            optimizer.RegisterPass(spvtools::CreateAggressiveDCEPass());
496            optimizer.RegisterPass(spvtools::CreateBlockMergePass());
497            optimizer.RegisterPass(spvtools::CreateRedundancyEliminationPass());
498            optimizer.RegisterPass(spvtools::CreateDeadBranchElimPass());
499            optimizer.RegisterPass(spvtools::CreateBlockMergePass());
500            optimizer.RegisterPass(spvtools::CreateSimplificationPass());
501
502            // We again run compaction to try and ensure the final output uses ids that are in
503            // range. On a complex shader, this reduced the amount ids by 5.
504            optimizer.RegisterPass(spvtools::CreateCompactIdsPass());
505
506            break;
507        }
508    }
509
510    if (debugInfoType != SLANG_DEBUG_INFO_LEVEL_NONE)
511    {
512        optimizer.RegisterPass(spvtools::CreateRedundantLineInfoElimPass());
513    }
514
515    spvOptOptions.set_run_validator(false); // Don't run the validator by default
516
517    {
518        // Put the output optimized spirv into optSpirv
519        std::vector<unsigned int> optSpirv;
520
521        // Optimize
522        if (optimizer.Run(ioSpirv.data(), ioSpirv.size(), &optSpirv, spvOptOptions))
523        {
524            assert(optSpirv.size() > 0);
525            // Make the ioSpirv the optimized spirv
526            ioSpirv.swap(optSpirv);
527        }
528    }
529}
530
531static int spirv_Optimize_1_2(const glslang_CompileRequest_1_2& request)
532{
533    std::vector<SPIRVOptimizationDiagnostic> diagnostics;
534    std::vector<uint32_t> spirvBuffer;
535    size_t inputBlobSize = (char*)request.inputEnd - (char*)request.inputBegin;
536    spirvBuffer.resize(inputBlobSize / sizeof(uint32_t));
537    memcpy(spirvBuffer.data(), request.inputBegin, inputBlobSize);
538
539    glslang_optimizeSPIRV(SPV_ENV_UNIVERSAL_1_5, request, diagnostics, spirvBuffer);
540    if (request.outputFunc)
541    {
542        request.outputFunc(
543            spirvBuffer.data(),
544            spirvBuffer.size() * sizeof(uint32_t),
545            request.outputUserData);
546    }
547    if (request.diagnosticFunc)
548    {
549        for (auto& diagnostic : diagnostics)
550        {
551            request.diagnosticFunc(
552                (void*)diagnostic.message.c_str(),
553                diagnostic.message.size() * sizeof(char),
554                request.diagnosticUserData);
555        }
556    }
557    return SLANG_OK;
558}
559
560static glslang::EShTargetLanguageVersion _makeTargetLanguageVersion(
561    int majorVersion,
562    int minorVersion)
563{
564    return glslang::EShTargetLanguageVersion(
565        (uint32_t(majorVersion) << 16) | (uint32_t(minorVersion) << 8));
566}
567
568static glsl_SPIRVVersion _toSPIRVVersion(glslang::EShTargetLanguageVersion version)
569{
570    glsl_SPIRVVersion ver;
571    ver.patch = 0;
572    ver.major = uint8_t(uint32_t(version) >> 16);
573    ver.minor = uint8_t(uint32_t(version) >> 8);
574    return ver;
575}
576
577// For working out the targets based on SPIR-V target strings
578
579namespace
580{ // anonymous
581
582struct SPRIVTargetInfo
583{
584    const char* name;
585    spv_target_env targetEnv;
586};
587
588} // namespace
589
590static const SPRIVTargetInfo kSpirvTargetInfos[] = {
591    {"1.0", SPV_ENV_UNIVERSAL_1_0},
592    {"vk1.0", SPV_ENV_VULKAN_1_0},
593    {"1.1", SPV_ENV_UNIVERSAL_1_1},
594    {"cl2.1", SPV_ENV_OPENCL_2_1},
595    {"cl2.2", SPV_ENV_OPENCL_2_2},
596    {"gl4.0", SPV_ENV_OPENGL_4_0},
597    {"gl4.1", SPV_ENV_OPENGL_4_1},
598    {"gl4.2", SPV_ENV_OPENGL_4_2},
599    {"gl4.3", SPV_ENV_OPENGL_4_3},
600    {"gl4.5", SPV_ENV_OPENGL_4_5},
601    {"1.2", SPV_ENV_UNIVERSAL_1_2},
602    {"cl1.2", SPV_ENV_OPENCL_1_2},
603    {"cl_emb1.2", SPV_ENV_OPENCL_EMBEDDED_1_2},
604    {"cl2.0", SPV_ENV_OPENCL_2_0},
605    {"cl_emb2.0", SPV_ENV_OPENCL_EMBEDDED_2_0},
606    {"cl_emb2.1", SPV_ENV_OPENCL_EMBEDDED_2_1},
607    {"cl_emb2.2", SPV_ENV_OPENCL_EMBEDDED_2_2},
608    {"1.3", SPV_ENV_UNIVERSAL_1_3},
609    {"vk1.1", SPV_ENV_VULKAN_1_1},
610    {"web_gpu1.0", SPV_ENV_WEBGPU_0},
611    {"1.4", SPV_ENV_UNIVERSAL_1_4},
612    {"vk1.1_spirv1.4", SPV_ENV_VULKAN_1_1_SPIRV_1_4},
613    {"1.5", SPV_ENV_UNIVERSAL_1_5},
614};
615
616static int _findTargetIndex(const char* name)
617{
618    const int count = int(sizeof(kSpirvTargetInfos) / sizeof(kSpirvTargetInfos[0]));
619    for (int i = 0; i < count; ++i)
620    {
621        const SPRIVTargetInfo& info = kSpirvTargetInfos[i];
622
623        if (::strcmp(info.name, name) == 0)
624        {
625            return i;
626        }
627    }
628    return -1;
629}
630
631static spv_target_env _getUniversalTargetEnv(glslang::EShTargetLanguageVersion inVersion)
632{
633    glsl_SPIRVVersion spirvVersion = _toSPIRVVersion(inVersion);
634    uint32_t ver = (uint32_t(spirvVersion.major) << 8) | spirvVersion.minor;
635
636    switch (ver)
637    {
638    case 0x100:
639        return SPV_ENV_UNIVERSAL_1_0;
640    case 0x101:
641        return SPV_ENV_UNIVERSAL_1_1;
642    case 0x102:
643        return SPV_ENV_UNIVERSAL_1_2;
644    case 0x103:
645        return SPV_ENV_UNIVERSAL_1_3;
646    case 0x104:
647        return SPV_ENV_UNIVERSAL_1_4;
648    case 0x105:
649        return SPV_ENV_UNIVERSAL_1_5;
650    case 0x106:
651        return SPV_ENV_UNIVERSAL_1_6;
652    default:
653        {
654            if (ver > 0x106)
655            {
656                // This is the highest we known for now..., so try that
657                return SPV_ENV_UNIVERSAL_1_6;
658            }
659            break;
660        }
661    }
662    // Just use the default...
663    return SPV_ENV_UNIVERSAL_1_2;
664}
665
666static int glslang_compileGLSLToSPIRV(glslang_CompileRequest_1_2 request)
667{
668    // Check that the encoding matches
669    assert(glslang::EShTargetSpv_1_4 == _makeTargetLanguageVersion(1, 4));
670
671    EShLanguage glslangStage;
672    switch (request.slangStage)
673    {
674#define CASE(SP, GL)                \
675    case SLANG_STAGE_##SP:          \
676        glslangStage = EShLang##GL; \
677        break
678        CASE(VERTEX, Vertex);
679        CASE(FRAGMENT, Fragment);
680        CASE(GEOMETRY, Geometry);
681        CASE(HULL, TessControl);
682        CASE(DOMAIN, TessEvaluation);
683        CASE(COMPUTE, Compute);
684
685        CASE(RAY_GENERATION, RayGenNV);
686        CASE(INTERSECTION, IntersectNV);
687        CASE(ANY_HIT, AnyHitNV);
688        CASE(CLOSEST_HIT, ClosestHitNV);
689        CASE(MISS, MissNV);
690        CASE(CALLABLE, CallableNV);
691
692        CASE(MESH, Mesh);
693        CASE(AMPLIFICATION, Task);
694#undef CASE
695
696    default:
697        dumpDiagnostics(request, "internal error: stage unsupported by glslang\n");
698        return 1;
699    }
700
701    spv_target_env targetEnv = SPV_ENV_UNIVERSAL_1_2;
702    glslang::EShTargetLanguageVersion targetLanguage = glslang::EShTargetLanguageVersion(0);
703
704    int spirvTargetIndex = -1;
705    if (request.spirvTargetName)
706    {
707        spirvTargetIndex = _findTargetIndex(request.spirvTargetName);
708        if (spirvTargetIndex < 0)
709        {
710            dumpDiagnostics(request, "warning: unknown SPIR-V version\n");
711        }
712        else
713        {
714            targetEnv = kSpirvTargetInfos[spirvTargetIndex].targetEnv;
715        }
716    }
717
718    // If a version is specified, and no target language is specified, set to universal version of
719    // that SPIR-V version
720    if (request.spirvVersion.major != 0 && targetLanguage == glslang::EShTargetLanguageVersion(0))
721    {
722        targetLanguage =
723            _makeTargetLanguageVersion(request.spirvVersion.major, request.spirvVersion.minor);
724    }
725
726    // If we don't have a target, but do have a language, use that to determine a universal target
727    if (spirvTargetIndex < 0 && targetLanguage != glslang::EShTargetLanguageVersion(0))
728    {
729        // We can just use the appropriate universal based on the target language
730        targetEnv = _getUniversalTargetEnv(targetLanguage);
731    }
732
733    // TODO: compute glslang stage to use
734
735    glslang::TShader* shader = new glslang::TShader(glslangStage);
736    auto shaderPtr = std::unique_ptr<glslang::TShader>(shader);
737
738    // Only set the target language if one is determined
739    if (targetLanguage != glslang::EShTargetLanguageVersion(0))
740    {
741        shader->setEnvTarget(glslang::EShTargetSpv, targetLanguage);
742    }
743
744    glslang::TProgram* program = new glslang::TProgram();
745    auto programPtr = std::unique_ptr<glslang::TProgram>(program);
746
747    char const* sourceText = (char const*)request.inputBegin;
748    char const* sourceTextEnd = (char const*)request.inputEnd;
749
750    int sourceTextLength = (int)(sourceTextEnd - sourceText);
751
752    shader->setPreamble("#extension GL_GOOGLE_cpp_style_line_directive : require\n");
753    shader->setStringsWithLengthsAndNames(&sourceText, &sourceTextLength, &request.sourcePath, 1);
754
755    // Options for compilation of glsl to Spv
756
757    // spvOptions ctors with default options (this it the same as passing nullptr to GlslangToSpv)
758    glslang::SpvOptions spvOptions;
759
760    const SlangDebugInfoLevel debugLevel = (SlangDebugInfoLevel)request.debugInfoType;
761
762    // Enable generation of debug info, if any debug level other than none is requested
763    if (debugLevel != SLANG_DEBUG_INFO_LEVEL_NONE)
764    {
765        spvOptions.generateDebugInfo = true;
766        spvOptions.emitNonSemanticShaderDebugInfo = true;
767        shader->setDebugInfo(true);
768    }
769
770    if (debugLevel == SLANG_DEBUG_INFO_LEVEL_MAXIMAL)
771    {
772        spvOptions.emitNonSemanticShaderDebugSource = true;
773        spvOptions.disableOptimizer = true;
774        request.optimizationLevel = SLANG_OPTIMIZATION_LEVEL_NONE;
775    }
776
777    // Link program
778    {
779        const EShMessages messages = EShMessages(EShMsgSpvRules | EShMsgVulkanRules);
780
781        if (!shader->parse(&gResources, 110, false, messages))
782        {
783            dumpDiagnostics(request, shader->getInfoLog());
784            return 1;
785        }
786
787        if (request.entryPointName && strlen(request.entryPointName))
788            shader->setEntryPoint(request.entryPointName);
789
790        program->addShader(shader);
791
792        if (!program->link(messages))
793        {
794            dumpDiagnostics(request, program->getInfoLog());
795            return 1;
796        }
797
798        if (!program->mapIO())
799        {
800            dumpDiagnostics(request, program->getInfoLog());
801            return 1;
802        }
803    }
804
805    for (int stage = 0; stage < EShLangCount; ++stage)
806    {
807        auto stageIntermediate = program->getIntermediate((EShLanguage)stage);
808        if (!stageIntermediate)
809            continue;
810        if (debugLevel == SLANG_DEBUG_INFO_LEVEL_MAXIMAL)
811        {
812            shader->addSourceText(sourceText, sourceTextLength);
813        }
814
815        std::vector<unsigned int> spirv;
816        spv::SpvBuildLogger logger;
817
818        // Copy options to make sure spvOptions not altered
819        glslang::SpvOptions copySpvOptions(spvOptions);
820
821        glslang::GlslangToSpv(*stageIntermediate, spirv, &logger, &copySpvOptions);
822
823        int optErrorCount = 0;
824
825        if (request.optimizationLevel != SLANG_OPTIMIZATION_LEVEL_NONE)
826        {
827            std::vector<SPIRVOptimizationDiagnostic> optDiags;
828            glslang_optimizeSPIRV(targetEnv, request, optDiags, spirv);
829
830            {
831                for (const auto& diag : optDiags)
832                {
833                    // Count the number of errors
834                    optErrorCount += int(diag.level <= SPV_MSG_ERROR);
835
836                    // Note this string does not have \n.
837                    std::string diagString = diag.toString();
838
839                    // Dump
840                    dump(
841                        diagString.c_str(),
842                        diagString.length(),
843                        request.diagnosticFunc,
844                        request.diagnosticUserData,
845                        stderr);
846                }
847            }
848        }
849
850        dumpDiagnostics(request, logger.getAllMessages());
851
852        dump(
853            spirv.data(),
854            spirv.size() * sizeof(unsigned int),
855            request.outputFunc,
856            request.outputUserData,
857            stdout);
858
859        if (optErrorCount > 0)
860        {
861            // It's an error...
862            return 1;
863        }
864    }
865
866    return 0;
867}
868
869static int glslang_dissassembleSPIRV(const glslang_CompileRequest_1_2& request)
870{
871    typedef unsigned int SPIRVWord;
872
873    SPIRVWord const* spirvBegin = (SPIRVWord const*)request.inputBegin;
874    SPIRVWord const* spirvEnd = (SPIRVWord const*)request.inputEnd;
875
876    std::vector<SPIRVWord> spirv(spirvBegin, spirvEnd);
877
878    std::string result;
879    spvtools::SpirvTools spirvTools(SPV_ENV_UNIVERSAL_1_5);
880    spirvTools.Disassemble(
881        spirv,
882        &result,
883        SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES | SPV_BINARY_TO_TEXT_OPTION_COMMENT);
884
885    dump(result.c_str(), result.length(), request.outputFunc, request.outputUserData, stdout);
886    return 0;
887}
888
889// We need a per process initialization
890class ProcessInitializer
891{
892public:
893    ProcessInitializer() { m_isInitialized = false; }
894
895    bool init()
896    {
897        std::lock_guard<std::mutex> guard(m_mutex);
898        if (!m_isInitialized)
899        {
900            if (!glslang::InitializeProcess())
901            {
902                return false;
903            }
904            m_isInitialized = true;
905        }
906        return true;
907    }
908
909    ~ProcessInitializer()
910    {
911        // We *assume* will only be called once dll is detatched and that will be on a single thread
912        if (m_isInitialized)
913        {
914            glslang::FinalizeProcess();
915        }
916    }
917
918    std::mutex m_mutex;
919    bool m_isInitialized = false;
920};
921
922static int _compile(const glslang_CompileRequest_1_2& request)
923{
924    int result = 0;
925    switch (request.action)
926    {
927    default:
928        result = 1;
929        break;
930
931    case GLSLANG_ACTION_COMPILE_GLSL_TO_SPIRV:
932        result = glslang_compileGLSLToSPIRV(request);
933        break;
934
935    case GLSLANG_ACTION_DISSASSEMBLE_SPIRV:
936        result = glslang_dissassembleSPIRV(request);
937        break;
938
939    case GLSLANG_ACTION_OPTIMIZE_SPIRV:
940        result = spirv_Optimize_1_2(request);
941        break;
942    }
943
944    return result;
945}
946
947extern "C"
948#ifdef _MSC_VER
949    _declspec(dllexport)
950#else
951    __attribute__((__visibility__("default")))
952#endif
953        int glslang_compile_1_2(glslang_CompileRequest_1_2* inRequest)
954{
955    static ProcessInitializer g_processInitializer;
956    if (!g_processInitializer.init())
957    {
958        // Failed
959        return 1;
960    }
961
962    // If it's the right size just use it
963    if (inRequest->sizeInBytes == sizeof(glslang_CompileRequest_1_2))
964    {
965        return _compile(*inRequest);
966    }
967    else
968    {
969        // NOTE! It could be larger, but here we'll assume thats ok, and copy and use.
970
971        // Try to ensure some binary compatibility, by using sizeInBytes member, and copying
972
973        glslang_CompileRequest_1_2 request;
974
975        // Copy into request
976        const size_t copySize =
977            (inRequest->sizeInBytes > sizeof(request)) ? sizeof(request) : inRequest->sizeInBytes;
978        ::memcpy(&request, inRequest, copySize);
979        // Zero any remaining members
980        memset(((uint8_t*)&request) + copySize, 0, sizeof(request) - copySize);
981
982        return _compile(request);
983    }
984}
985
986extern "C"
987#ifdef _MSC_VER
988    _declspec(dllexport)
989#else
990    __attribute__((__visibility__("default")))
991#endif
992        int glslang_compile_1_1(glslang_CompileRequest_1_1* inRequest)
993{
994    glslang_CompileRequest_1_2 request;
995    memset(&request, 0, sizeof(request));
996    request.sizeInBytes = sizeof(request);
997    request.set(*inRequest);
998    return glslang_compile_1_2(&request);
999}
1000
1001extern "C"
1002#ifdef _MSC_VER
1003    _declspec(dllexport)
1004#else
1005    __attribute__((__visibility__("default")))
1006#endif
1007        int glslang_compile(glslang_CompileRequest_1_0* inRequest)
1008{
1009    glslang_CompileRequest_1_1 request;
1010    memset(&request, 0, sizeof(request));
1011    request.sizeInBytes = sizeof(request);
1012    request.set(*inRequest);
1013    return glslang_compile_1_1(&request);
1014}
1015
1016extern "C"
1017#ifdef _MSC_VER
1018    _declspec(dllexport)
1019#else
1020    __attribute__((__visibility__("default")))
1021#endif
1022        int glslang_linkSPIRV(glslang_LinkRequest* request)
1023{
1024    if (!request || !request->modules || request->linkResult)
1025        return false;
1026
1027    try
1028    {
1029        spvtools::Context context(SPV_ENV_UNIVERSAL_1_5);
1030        spvtools::LinkerOptions options = {};
1031
1032        options.SetUseHighestVersion(true);
1033
1034        spvtools::MessageConsumer consumer = [](spv_message_level_t level,
1035                                                const char* source,
1036                                                const spv_position_t& position,
1037                                                const char* message)
1038        {
1039            printf("SPIRV-TOOLS: %s\n", message);
1040            printf("SPIRV-TOOLS: %s\n", source);
1041            printf("SPIRV-TOOLS: %zu:%zu\n", position.index, position.column);
1042        };
1043        context.SetMessageConsumer(consumer);
1044
1045        std::vector<std::vector<uint32_t>> moduleVecs(request->moduleCount);
1046        std::vector<const uint32_t*> moduleData(request->moduleCount);
1047        std::vector<size_t> moduleSizes(request->moduleCount);
1048
1049        for (size_t i = 0; i < request->moduleCount; ++i)
1050        {
1051            moduleData[i] = request->modules[i];
1052            moduleSizes[i] = request->moduleSizes[i];
1053        }
1054
1055        std::vector<uint32_t> linkedBinary;
1056        spv_result_t success = spvtools::Link(
1057            context,
1058            moduleData.data(),
1059            moduleSizes.data(),
1060            request->moduleCount,
1061            &linkedBinary,
1062            options);
1063
1064        if (success == SPV_SUCCESS)
1065        {
1066            request->linkResult = new uint32_t[linkedBinary.size()];
1067            memcpy(
1068                (void*)request->linkResult,
1069                linkedBinary.data(),
1070                linkedBinary.size() * sizeof(uint32_t));
1071            request->linkResultSize = linkedBinary.size();
1072        }
1073
1074        return success == SPV_SUCCESS;
1075    }
1076    catch (...)
1077    {
1078        return false;
1079    }
1080}