yum-mirror/slang

Making it easier to work with shaders

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

Ellie HermaszewskaMove switch statement bodies to their own lines (#5493)b118451e3

master
11.8 KiB416 linesraw
1#include "slang-source-embed-util.h"
2
3// Artifact
4#include "../compiler-core/slang-artifact-desc-util.h"
5#include "../compiler-core/slang-artifact-util.h"
6#include "../core/slang-blob.h"
7#include "../core/slang-char-util.h"
8#include "../core/slang-io.h"
9#include "../core/slang-string-escape-util.h"
10#include "../core/slang-string-util.h"
11
12namespace Slang
13{
14
15namespace
16{ // anonymous
17typedef SourceEmbedUtil::Style Style;
18} // namespace
19
20static const NamesDescriptionValue kSourceEmbedStyleInfos[] = {
21    {ValueInt(Style::None), "none", "No source level embedding"},
22    {ValueInt(Style::Default), "default", "The default embedding for the type to be embedded"},
23    {ValueInt(Style::Text),
24     "text",
25     "Embed as text. May change line endings. If output isn't text will use 'default'. Size will "
26     "*not* contain terminating 0."},
27    {ValueInt(Style::BinaryText), "binary-text", "Embed as text assuming contents is binary. "},
28    {ValueInt(Style::U8), "u8", "Embed as unsigned bytes."},
29    {ValueInt(Style::U16), "u16", "Embed as uint16_t."},
30    {ValueInt(Style::U32), "u32", "Embed as uint32_t."},
31    {ValueInt(Style::U64), "u64", "Embed as uint64_t."},
32};
33
34/* static */ ConstArrayView<NamesDescriptionValue> SourceEmbedUtil::getStyleInfos()
35{
36    return makeConstArrayView(kSourceEmbedStyleInfos);
37}
38
39/* static */ bool SourceEmbedUtil::isSupported(SlangSourceLanguage lang)
40{
41    return lang == SLANG_SOURCE_LANGUAGE_CPP || lang == SLANG_SOURCE_LANGUAGE_C;
42}
43
44static bool _isHeaderExtension(const UnownedStringSlice& in)
45{
46    // Some "typical" header extensions
47    return in == toSlice("h") || in == toSlice("hpp") || in == toSlice("hxx") ||
48           in == toSlice("h++") || in == toSlice("hh");
49}
50
51/* static */ String SourceEmbedUtil::getPath(const String& path, const Options& options)
52{
53    if (!isSupported(options.language))
54    {
55        return String();
56    }
57
58    if (!path.getLength())
59    {
60        return path;
61    }
62
63    const auto ext = Path::getPathExt(path);
64
65    if (_isHeaderExtension(ext.getUnownedSlice()))
66    {
67        return path;
68    }
69
70    // Assume it's a header, and just use the .h extension
71    StringBuilder buf;
72    buf << path << toSlice(".h");
73    return buf;
74}
75
76/* static */ SourceEmbedUtil::Style SourceEmbedUtil::getDefaultStyle(const ArtifactDesc& desc)
77{
78    if (ArtifactDescUtil::isText(desc))
79    {
80        return Style::Text;
81    }
82
83    if (isDerivedFrom(desc.kind, ArtifactKind::CompileBinary))
84    {
85        // SPIR-V is encoded as U32
86        if (isDerivedFrom(desc.payload, ArtifactPayload::SPIRV))
87        {
88            return Style::U32;
89        }
90    }
91
92    // When in doube encode as U8 bytes.
93    // The problem is on some compilers there are limits on how long a U8 based binary can be.
94    return Style::U8;
95}
96
97// True if we need to copy into a buffer. Necessary if there is an alignement
98// issue or if there is a partial entry
99static bool _needsCopy(const uint8_t* cur, Count bytesPerElement, Count bytesPerLine)
100{
101    return ((size_t(bytesPerLine) | size_t(cur)) & size_t(bytesPerElement - 1)) != 0;
102}
103
104// NOTE! Assumes T is an unsigned type. Behavior will be incorrect if it is not.
105template<typename T>
106static void _appendHex(
107    const T* in,
108    ArrayView<char> elementWork,
109    char* dst,
110    size_t bytesForLine,
111    StringBuilder& out)
112{
113    // Check that T is unsigned
114    SLANG_COMPILE_TIME_ASSERT((T(~T(0))) > T(0));
115
116    // Make sure dst seems plausible
117    SLANG_ASSERT(dst >= elementWork.begin() && dst <= elementWork.end());
118    // Check the alignment
119    SLANG_ASSERT((size_t(in) & (sizeof(T) - 1)) == 0);
120
121    // Calculate the amount of elements for this line.
122    const size_t elementsCount = (bytesForLine + sizeof(T) - 1) / sizeof(T);
123
124    // The amount of hex digits needed, is 2 per byte
125    const Count numHexDigits = sizeof(T) * 2;
126
127    // Shift to get top nybble
128    const Index shift = (numHexDigits - 1) * 4;
129
130    for (size_t i = 0; i < elementsCount; ++i)
131    {
132        T value = in[i];
133
134        for (Index j = 0; j < numHexDigits; j++, value <<= 4)
135        {
136            dst[j] = CharUtil::getHexChar(Index(value >> shift) & 0xf);
137        }
138
139        out.append(elementWork.getBuffer(), elementWork.getCount());
140    }
141}
142
143static SlangResult _append(
144    const SourceEmbedUtil::Options& options,
145    ConstArrayView<uint8_t> data,
146    StringBuilder& buf)
147{
148    const uint8_t* cur = data.begin();
149
150    const auto prefix = toSlice("0x");
151    const auto suffix = toSlice(", ");
152    UnownedStringSlice literalSuffix;
153
154    UnownedStringSlice elementType;
155
156    Count bytesPerElement;
157
158    switch (options.style)
159    {
160    case Style::U8:
161        {
162            elementType = toSlice("unsigned char");
163            bytesPerElement = 1;
164            break;
165        }
166    case Style::U16:
167        {
168            elementType = toSlice("uint16_t");
169            bytesPerElement = 2;
170            break;
171        }
172    case Style::U32:
173        {
174            elementType = toSlice("uint32_t");
175            bytesPerElement = 4;
176            break;
177        }
178    case Style::U64:
179        {
180            elementType = toSlice("uint64_t");
181            bytesPerElement = 8;
182            // On testing on GCC/CLANG/Recent VS, there is no warning/error without suffix, so
183            // will leave off for now.
184            // literalSuffix = toSlice("ULL");
185            break;
186        }
187    default:
188        return SLANG_FAIL;
189    }
190
191    // Output the variable
192
193    buf << "const " << elementType << " " << options.variableName << "[] = \n";
194    buf << "{\n";
195
196    // Work out the element work
197    char work[80];
198    Count elementSizeInChars;
199    {
200        StringBuilder workBuf;
201        workBuf << prefix;
202        workBuf.appendRepeatedChar('N', 2 * bytesPerElement);
203        workBuf << literalSuffix;
204        workBuf << suffix;
205
206        elementSizeInChars = workBuf.getLength();
207        ::memcpy(work, workBuf.getBuffer(), elementSizeInChars);
208    }
209
210    auto workView = makeArrayView(work, elementSizeInChars);
211    char* dstChars = work + prefix.getLength();
212
213    Count elementsPerLine = (options.lineLength - options.indent.getLength()) / elementSizeInChars;
214    elementsPerLine = (elementsPerLine <= 0) ? 1 : elementsPerLine;
215
216    // Maximum bytes output per line
217    const size_t bytesPerLine = elementsPerLine * bytesPerElement;
218
219    List<uint64_t> alignedElements;
220    alignedElements.setCount(Count((bytesPerLine / sizeof(uint64_t)) + 2));
221    uint8_t* alignedDst = (uint8_t*)alignedElements.getBuffer();
222
223    size_t bytesRemaining = data.getCount();
224
225    while (bytesRemaining > 0)
226    {
227        const size_t bytesForLine = bytesRemaining > bytesPerLine ? bytesPerLine : bytesRemaining;
228        bytesRemaining -= bytesForLine;
229
230        const uint8_t* lineBytes = cur;
231        cur += bytesForLine;
232
233        // We copy if we want alignment of if we hit a partial at the end
234        if (_needsCopy(lineBytes, bytesPerElement, bytesForLine))
235        {
236            // Make sure the last element is zeroed, before copying
237            // Needed if the last element is partial.
238            alignedElements[Index(bytesForLine / sizeof(uint64_t))] = 0;
239
240            // Copy the bytes over
241            ::memcpy(alignedDst, lineBytes, bytesForLine);
242
243            // Use the aligned buffer for the line
244            lineBytes = alignedDst;
245        }
246
247        buf << options.indent;
248
249        switch (bytesPerElement)
250        {
251        case 1:
252            _appendHex<uint8_t>(lineBytes, workView, dstChars, bytesForLine, buf);
253            break;
254        case 2:
255            _appendHex<uint16_t>((const uint16_t*)lineBytes, workView, dstChars, bytesForLine, buf);
256            break;
257        case 4:
258            _appendHex<uint32_t>((const uint32_t*)lineBytes, workView, dstChars, bytesForLine, buf);
259            break;
260        case 8:
261            _appendHex<uint64_t>((const uint64_t*)lineBytes, workView, dstChars, bytesForLine, buf);
262            break;
263        }
264
265        buf << "\n";
266    }
267
268    buf << "};\n\n";
269
270    return SLANG_OK;
271}
272
273/* static */ SlangResult SourceEmbedUtil::createEmbedded(
274    IArtifact* artifact,
275    const Options& inOptions,
276    ComPtr<IArtifact>& outArtifact)
277{
278    if (!isSupported(inOptions.language))
279    {
280        return SLANG_E_NOT_IMPLEMENTED;
281    }
282
283    ComPtr<ISlangBlob> blob;
284    SLANG_RETURN_ON_FAIL(artifact->loadBlob(ArtifactKeep::No, blob.writeRef()));
285
286    const auto desc = artifact->getDesc();
287
288    Options options(inOptions);
289
290    // If the style is text, but the artifact *isn't* a text type, we'll
291    // use 'default' for the type
292    if (options.style == Style::Text && !ArtifactDescUtil::isText(desc))
293    {
294        options.style = Style::Default;
295    }
296
297    if (options.style == Style::Default)
298    {
299        options.style = getDefaultStyle(desc);
300    }
301
302    // If there is no style there is nothing to do
303    if (options.style == Style::None)
304    {
305        return SLANG_OK;
306    }
307
308    if (options.variableName.getLength() <= 0)
309    {
310        options.variableName = "data";
311    }
312
313    StringBuilder buf;
314
315    ConstArrayView<uint8_t> data((const uint8_t*)blob->getBufferPointer(), blob->getBufferSize());
316
317    size_t totalSizeInBytes = data.getCount();
318
319    switch (options.style)
320    {
321    case Style::Text:
322        {
323            totalSizeInBytes = 0;
324
325            auto handler = StringEscapeUtil::getHandler(StringEscapeUtil::Style::Cpp);
326
327            buf << "const char " << options.variableName << "[] = \n";
328
329            // Split into lines
330            // We dont worry about splitting lines in this impl...
331            UnownedStringSlice text((const char*)data.begin(), data.getCount());
332
333            for (auto line : LineParser(text))
334            {
335                buf << options.indent;
336                buf << "\"";
337
338                handler->appendEscaped(line, buf);
339
340                // Work out the total size, taking into account we may encode line endings and \0
341                // differently The +1 is for \n
342                totalSizeInBytes += line.getLength() + 1;
343
344                buf << "\\n\"\n";
345            }
346
347            buf << ";\n";
348            break;
349        }
350    case Style::BinaryText:
351        {
352            auto handler = StringEscapeUtil::getHandler(StringEscapeUtil::Style::Cpp);
353
354            buf << "const char " << options.variableName << "[] = \n";
355
356            // We could encode everything and then split
357            // but if we do that we probably want to not split across an escaped character,
358            // although that may be handled correctly.
359
360            // The other way to this is incrementally, so that's what we will do here
361            UnownedStringSlice text((const char*)data.begin(), data.getCount());
362
363            auto cur = text.begin();
364            auto end = text.end();
365
366            while (cur < end)
367            {
368                const auto startOffset = buf.getLength();
369
370                buf << options.indent;
371                buf << "\"";
372
373                do
374                {
375                    handler->appendEscaped(UnownedStringSlice(cur, 1), buf);
376                    cur++;
377                } while (buf.getLength() - startOffset < options.lineLength - 1);
378
379                buf << "\"\n";
380            }
381
382            buf << ";\n";
383            break;
384        }
385    case Style::U8:
386    case Style::U16:
387    case Style::U32:
388    case Style::U64:
389        {
390            SLANG_RETURN_ON_FAIL(_append(options, data, buf));
391            break;
392        }
393    default:
394        {
395            return SLANG_E_NOT_IMPLEMENTED;
396        }
397    }
398
399    buf << "const size_t " << options.variableName
400        << "_sizeInBytes = " << uint64_t(totalSizeInBytes) << ";\n\n";
401
402    // Make into an artifact
403    ArtifactPayload payload =
404        options.language == SLANG_SOURCE_LANGUAGE_C ? ArtifactPayload::C : ArtifactPayload::Cpp;
405    auto dstDesc = ArtifactDesc::make(ArtifactKind::Source, payload);
406
407    auto dstArtifact = ArtifactUtil::createArtifact(dstDesc);
408
409    auto dstBlob = StringBlob::moveCreate(buf);
410    dstArtifact->addRepresentationUnknown(dstBlob);
411
412    outArtifact = dstArtifact;
413    return SLANG_OK;
414}
415
416} // namespace Slang