yum-mirror/slang

Making it easier to work with shaders

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

Theresa FoleyCleanups related to RIFF support (#7041)4c76b2759

master
9.9 KiB360 linesraw
1// unit-test-riff.cpp
2
3#include "../../source/core/slang-random-generator.h"
4#include "../../source/core/slang-riff.h"
5#include "unit-test/slang-unit-test.h"
6
7using namespace Slang;
8
9static void _writeRandom(
10    RandomGenerator* rand,
11    size_t maxSize,
12    RIFF::BuildCursor& cursor,
13    List<uint8_t>& ioData)
14{
15    while (true)
16    {
17        const Index oldCount = ioData.getCount();
18
19        const size_t allocSize = size_t(rand->nextInt32InRange(1, 50));
20
21        if (allocSize + oldCount > maxSize)
22        {
23            break;
24        }
25
26        ioData.setCount(oldCount + Index(allocSize));
27        rand->nextData(ioData.getBuffer() + oldCount, allocSize);
28
29        // Write
30        cursor.addData(ioData.getBuffer() + oldCount, allocSize);
31    }
32
33    // Should be a single block with same data as the List
34    auto dataChunk = as<RIFF::DataChunkBuilder>(cursor.getCurrentChunk());
35    SLANG_ASSERT(dataChunk);
36}
37
38namespace
39{
40struct DumpContext
41{
42private:
43    WriterHelper _writer;
44    Count _indent = 0;
45    Count _hexByteCount = 0;
46    bool _isRoot = true;
47
48public:
49    DumpContext(ISlangWriter* writer)
50        : _writer(writer)
51    {
52    }
53
54    void beginListChunk(RIFF::Chunk::Type type)
55    {
56        _dumpIndent();
57        // If it's the root it's 'riff'
58        _dumpRiffType(_isRoot ? RIFF::RootChunk::kTag : RIFF::ListChunk::kTag);
59        _writer.put(" ");
60        _dumpRiffType(type);
61        _writer.put("\n");
62        _indent++;
63    }
64
65    void endListChunk() { _indent--; }
66
67    void beginDataChunk(RIFF::Chunk::Type type)
68    {
69        _dumpIndent();
70        // Write out the name
71        _dumpRiffType(type);
72        _writer.put("\n");
73        _indent++;
74
75        _hexByteCount = 0;
76    }
77
78    void endDataChunk() { _indent--; }
79
80    void handleData(void const* data, Size size)
81    {
82        auto cursor = static_cast<Byte const*>(data);
83        auto remainingSize = size;
84        while (remainingSize--)
85        {
86            auto byte = *cursor++;
87
88            static const Count kBytesPerLine = 32;
89            static const Count kBytesPerCluster = 4;
90            if (_hexByteCount % kBytesPerLine == 0)
91            {
92                _writer.put("\n");
93                _dumpIndent();
94            }
95            else if (_hexByteCount % kBytesPerCluster == 0)
96            {
97                _writer.put(" ");
98            }
99            _hexByteCount++;
100
101            char text[4] = {0, 0, ' ', 0};
102
103            char const* hexDigits = "0123456789abcdef";
104            text[0] = hexDigits[(byte >> 4) & 0xF];
105            text[1] = hexDigits[(byte >> 0) & 0xF];
106
107            _writer.put(text);
108        }
109    }
110
111    void _dumpIndent()
112    {
113        for (int i = 0; i < _indent; ++i)
114        {
115            _writer.put("  ");
116        }
117    }
118    void _dumpRiffType(FourCC fourCC)
119    {
120        auto rawValue = FourCC::RawValue(fourCC);
121
122        char text[5];
123        for (int i = 0; i < 4; ++i)
124        {
125            text[i] = char(rawValue & 0xFF);
126            rawValue >>= 8;
127        }
128        text[4] = 0;
129        _writer.put(text);
130    }
131};
132
133} // namespace
134
135static void _dump(RIFF::Chunk const* chunk, DumpContext context)
136{
137    if (auto listChunk = as<RIFF::ListChunk>(chunk))
138    {
139        context.beginListChunk(listChunk->getType());
140        for (auto child : listChunk->getChildren())
141            _dump(child, context);
142        context.endListChunk();
143    }
144    else if (auto dataChunk = as<RIFF::DataChunk>(chunk))
145    {
146        context.beginDataChunk(dataChunk->getType());
147        context.handleData(dataChunk->getPayload(), dataChunk->getPayloadSize());
148        context.endDataChunk();
149    }
150}
151
152static void _dump(RIFF::ChunkBuilder* chunk, DumpContext context)
153{
154    if (auto listChunk = as<RIFF::ListChunkBuilder>(chunk))
155    {
156        context.beginListChunk(listChunk->getType());
157        for (auto child : listChunk->getChildren())
158            _dump(child, context);
159        context.endListChunk();
160    }
161    else if (auto dataChunk = as<RIFF::DataChunkBuilder>(chunk))
162    {
163        context.beginDataChunk(dataChunk->getType());
164        for (auto shard : dataChunk->getShards())
165            context.handleData(shard->getPayload(), shard->getPayloadSize());
166        context.endDataChunk();
167    }
168}
169
170static bool _isSingleShard(RIFF::DataChunkBuilder* chunk)
171{
172    Count count = 0;
173    for (auto shard : chunk->getShards())
174    {
175        count++;
176        if (count > 1)
177            break;
178    }
179    return count == 1;
180}
181
182static bool _isEqual(RIFF::DataChunkBuilder* chunk, void const* data, Size size)
183{
184    auto remainingData = static_cast<Byte const*>(data);
185    auto remainingSize = size;
186
187    for (auto shard : chunk->getShards())
188    {
189        // If there is more content in the chunk than remains
190        // to compare against, then there is no chance of a match.
191        //
192        auto shardSize = shard->getPayloadSize();
193        if (shard->getPayloadSize() > remainingSize)
194        {
195            return false;
196        }
197
198        // Contents must match, byte-for-byte.
199        //
200        if (::memcmp(remainingData, shard->getPayload(), shardSize) != 0)
201        {
202            return false;
203        }
204
205        remainingData += shardSize;
206        remainingSize -= shardSize;
207    }
208
209    // If we reach the end of the chunk, then we have
210    // a match if there is no data remaining to
211    // compare against.
212    //
213    return remainingSize == 0;
214}
215
216SLANG_UNIT_TEST(riff)
217{
218    const FourCC markThings = SLANG_FOUR_CC('T', 'H', 'I', 'N');
219    const FourCC markData = SLANG_FOUR_CC('D', 'A', 'T', 'A');
220
221    {
222        RIFF::Builder riffBuilder;
223        RIFF::BuildCursor cursor(riffBuilder);
224
225        {
226            SLANG_SCOPED_RIFF_BUILDER_LIST_CHUNK(cursor, markThings);
227            {
228                SLANG_SCOPED_RIFF_BUILDER_DATA_CHUNK(cursor, markData);
229
230                const char hello[] = "Hello ";
231                const char world[] = "World!";
232
233                cursor.addData(hello, sizeof(hello));
234                cursor.addData(world, sizeof(world));
235            }
236
237            {
238                SLANG_SCOPED_RIFF_BUILDER_DATA_CHUNK(cursor, markData);
239
240                const char test0[] = "Testing... ";
241                const char test1[] = "Testing!";
242
243                cursor.addData(test0, sizeof(test0));
244                cursor.addData(test1, sizeof(test1));
245            }
246
247            {
248                SLANG_SCOPED_RIFF_BUILDER_LIST_CHUNK(cursor, markThings);
249
250                {
251                    SLANG_SCOPED_RIFF_BUILDER_DATA_CHUNK(cursor, markData);
252
253                    const char another[] = "Another?";
254                    cursor.addData(another, sizeof(another));
255                }
256            }
257        }
258
259        SLANG_CHECK(cursor.getCurrentChunk() == nullptr);
260        SLANG_CHECK(riffBuilder.getRootChunk() != nullptr);
261
262        {
263            StringBuilder builder;
264            {
265                StringWriter writer(&builder);
266                _dump(riffBuilder.getRootChunk(), &writer);
267            }
268
269            {
270                ComPtr<ISlangBlob> blob;
271                SLANG_CHECK(SLANG_SUCCEEDED(riffBuilder.writeToBlob(blob.writeRef())));
272
273                auto rootChunk = RIFF::RootChunk::getFromBlob(blob);
274                SLANG_CHECK(rootChunk != nullptr);
275
276                // Dump the read contents
277                StringBuilder readBuilder;
278                {
279                    StringWriter writer(&readBuilder, 0);
280                    _dump(rootChunk, &writer);
281                }
282
283                // They should be the same
284                SLANG_CHECK(readBuilder == builder);
285            }
286        }
287    }
288
289    // Test writing as a stream only allocates a single data block (as long as there is enough
290    // space).
291    {
292        RIFF::Builder builder;
293        RIFF::BuildCursor cursor(builder);
294
295        SLANG_SCOPED_RIFF_BUILDER_LIST_CHUNK(cursor, markThings);
296        {
297            SLANG_SCOPED_RIFF_BUILDER_DATA_CHUNK(cursor, markData);
298
299            RefPtr<RandomGenerator> rand = RandomGenerator::create(0x345234);
300
301            List<uint8_t> data;
302            _writeRandom(rand, builder._getMemoryArena().getBlockPayloadSize() / 2, cursor, data);
303
304            // Should be a single block with same data as the List
305            RIFF::DataChunkBuilder* dataChunk =
306                as<RIFF::DataChunkBuilder>(cursor.getCurrentChunk());
307            SLANG_ASSERT(dataChunk);
308
309            // It should be a single shard
310            SLANG_CHECK(_isSingleShard(dataChunk));
311
312            SLANG_CHECK(_isEqual(dataChunk, data.getBuffer(), data.getCount()));
313        }
314    }
315
316    // Test writing across multiple data blocks
317    {
318        RefPtr<RandomGenerator> rand = RandomGenerator::create(0x345234);
319
320        for (Int i = 0; i < 100; ++i)
321        {
322            RIFF::Builder builder;
323            RIFF::BuildCursor cursor(builder);
324
325            const size_t maxSize = rand->nextInt32InRange(
326                1,
327                int32_t(builder._getMemoryArena().getBlockPayloadSize() * 3));
328
329            SLANG_SCOPED_RIFF_BUILDER_LIST_CHUNK(cursor, markThings);
330            {
331                SLANG_SCOPED_RIFF_BUILDER_DATA_CHUNK(cursor, markData);
332
333                List<uint8_t> data;
334                _writeRandom(rand, maxSize, cursor, data);
335
336                // Should be a single block with same data as the List
337                RIFF::DataChunkBuilder* dataChunk =
338                    as<RIFF::DataChunkBuilder>(cursor.getCurrentChunk());
339                SLANG_CHECK(dataChunk && _isEqual(dataChunk, data.getBuffer(), data.getCount()));
340            }
341        }
342    }
343
344#if 0
345    {
346        RiffContainer container;
347        {
348            FileStream readStream("ambient-drop.wav", FileMode::Open, FileAccess::Read, FileShare::ReadWrite);
349            SLANG_CHECK(SLANG_SUCCEEDED(RiffUtil::read(&readStream, container)));
350            RiffUtil::dump(container.getRoot(), StdWriters::getOut());
351        }
352        // Write it
353        {
354
355            FileStream writeStream("check.wav", FileMode::Create, FileAccess::Write, FileShare::ReadWrite);
356            SLANG_CHECK(SLANG_SUCCEEDED(RiffUtil::write(container.getRoot(), true, &writeStream)));
357        }
358    }
359#endif
360}