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
25.7 KiB878 linesraw
1// slang-riff.cpp
2#include "slang-riff.h"
3
4#include "slang-blob.h"
5#include "slang-com-helper.h"
6
7namespace Slang
8{
9namespace RIFF
10{
11
12Size _roundUpToChunkAlignment(Size size)
13{
14    auto alignmentMask = Size(Chunk::kChunkAlignment) - 1;
15    return (size + alignmentMask) & ~alignmentMask;
16}
17
18//
19// RIFF::Chunk
20//
21
22//
23// RIFF::DataChunk
24//
25
26void DataChunk::writePayloadInto(void* outData, Size size) const
27{
28    SLANG_ASSERT(size <= getPayloadSize());
29    ::memcpy(outData, getPayload(), size);
30}
31
32//
33// RIFF::BoundsCheckedChunkPtr
34//
35
36void BoundsCheckedChunkPtr::_set(Chunk const* chunk, Size sizeLimit)
37{
38    // We start by clearing out the state of this
39    // pointer, so that we can early-out if any
40    // validation checks fail, and be sure we
41    // have a null pointer.
42    //
43    _ptr = nullptr;
44    _sizeLimit = 0;
45
46    // If there's nothing to point to, then the pointer
47    // should be null anyway.
48    //
49    if (!chunk || !sizeLimit)
50    {
51        return;
52    }
53
54    // Because this type can be used to traverse RIFF
55    // chunks that were loaded into memory from in-theory
56    // untrusted sources, we try to provide some validation
57    // checks to make sure that access to the chunk will
58    // be safe (or as safe as we can easily ensure).
59
60    // If the available size isn't even enough for the
61    // header of a RIFF chunk, then something is wrong.
62    //
63    if (sizeLimit < sizeof(Chunk::Header))
64    {
65        SLANG_UNEXPECTED("invalid RIFF");
66        return;
67    }
68
69    // Once we've checked that there is enough space
70    // for a valid RIFF header, we can read the
71    // size that the `chunk` reports itself as having.
72    //
73    auto reportedSize = chunk->getTotalSize();
74
75    // If the reported size is too small, then something
76    // is wrong.
77    //
78    if (reportedSize < sizeof(Chunk::Header))
79    {
80        SLANG_UNEXPECTED("invalid RIFF");
81        return;
82    }
83
84    // If the reported size is bigger than the size limit,
85    // then it must be invalid (it is reporting itself as
86    // bigger than the region of memory that is supposed
87    // to contain it).
88    //
89    if (reportedSize > sizeLimit)
90    {
91        SLANG_UNEXPECTED("invalid RIFF");
92        return;
93    }
94
95    // If the chunk claims to be a list chunk, then it
96    // must be big enough to hold the larger header
97    // that list chunks use.
98    //
99    if (as<ListChunk>(chunk))
100    {
101        if (reportedSize < sizeof(ListChunk::Header))
102        {
103            SLANG_UNEXPECTED("invalid RIFF");
104            return;
105        }
106    }
107
108    // At this point we've performed some basic validation
109    // telling us that the chunk header appears plausible.
110    // This does not mean that we've fully validated the
111    // hierarchy of child chunks under it (in the case of
112    // a list chunk), but that validation can be performed
113    // on-demand while descending the hierarchy.
114
115    _ptr = chunk;
116    _sizeLimit = sizeLimit;
117}
118
119void BoundsCheckedChunkPtr::_set(Chunk const* chunk)
120{
121    // In the case where we are being set to point to a
122    // single chunk, we have to assume that whatever
123    // code derived the `chunk` pointer has validated
124    // that it is safe to access its header.
125    //
126    // We will simply set up a pointer that can reference
127    // the `chunk` itself, as well as any of its children
128    // (if it has any), but that cannot be used to access
129    // further sibling chunks under the same parent.
130    //
131    _set(chunk, chunk->getTotalSize());
132}
133
134BoundsCheckedChunkPtr BoundsCheckedChunkPtr::getNextSibling() const
135{
136    SLANG_ASSERT(_ptr != nullptr);
137    if (!_ptr)
138        return nullptr;
139
140    // The RIFF chunk reports its own size, and when navigating
141    // the children of a list chunk, each child chunk starts
142    // at the next (aligned) offset after the previous one.
143    //
144    auto chunkSize = _ptr->getTotalSize();
145
146    // As a simple validation check, we check for a chunk that
147    // reports its size as something bigger than the available
148    // size; that would represent an invalid input.
149    //
150    if (chunkSize > _sizeLimit)
151    {
152        SLANG_UNEXPECTED("invalid RIFF chunk size");
153        UNREACHABLE_RETURN(nullptr);
154    }
155
156    // The next chunk (if there is one) would start at the
157    // next offset after this chunk, rounded up to the minimum
158    // alignment for a chunk. Thus, we round up the reported
159    // size of this chunk to compute the offset to the next
160    // chunk.
161    //
162    auto offsetToNextChunk = _roundUpToChunkAlignment(chunkSize);
163
164    // If stepping forward by the given number of bytes would
165    // cause us to exceed our size limit, then we have reached
166    // the end of the list of sibling chunks, and should
167    // return a null pointer.
168    //
169    if (offsetToNextChunk >= _sizeLimit)
170        return nullptr;
171
172    auto nextChunk = (RIFF::Chunk const*)(offsetToNextChunk + (Byte const*)_ptr);
173    auto nextSizeLimit = _sizeLimit - offsetToNextChunk;
174
175    return BoundsCheckedChunkPtr(nextChunk, nextSizeLimit);
176}
177
178
179//
180// RIFF::ListChunk
181//
182
183BoundsCheckedChunkPtr ListChunk::getFirstChild() const
184{
185    // Because this type could be used to navigate
186    // an untrusted RIFF that has been loaded into memory,
187    // we make some efforts to validate that things
188    // seem okay as we navigate it.
189
190    // The first child of a list chunk (if it has any)
191    // comes right after the list header.
192    //
193    Size firstChildOffset = sizeof(ListChunk::Header);
194
195    // The size that the parent chunk reports should
196    // be appropriate to store the header.
197    //
198    // Note that in order to compute the reported size
199    // we are *accessing* the header, so if there really
200    // are too few bytes available, it is up to whatever
201    // code computed this `ListChunk*` to have done
202    // their own validation checks.
203    //
204    Size reportedParentSize = getTotalSize();
205    if (reportedParentSize < firstChildOffset)
206    {
207        SLANG_UNEXPECTED("invalid RIFF");
208        UNREACHABLE_RETURN(nullptr);
209    }
210
211    // The total size that the childen of this chunk can
212    // consume is all of the reported size of the parent,
213    // after the `ListChunk::Header`.
214    //
215    Size availableSizeForChildren = reportedParentSize - firstChildOffset;
216
217    // The available size can be zero, in the case where
218    // the parent chunk has no children.
219    //
220    if (availableSizeForChildren == 0)
221        return nullptr;
222
223    // If the parent chunk has a non-zero size, then it should
224    // have at least one child, and the available size had better
225    // be big enough to at least hold the *header* of that first
226    // child.
227    //
228    if (availableSizeForChildren < sizeof(Chunk::Header))
229    {
230        SLANG_UNEXPECTED("invalid RIFF");
231        UNREACHABLE_RETURN(nullptr);
232    }
233
234    // At this point we've convinced ourselves that there is
235    // conceivably enough space for at least one child chunk,
236    // so we will form a `BoundsCheckedChunkPtr` to it, which
237    // will trigger further validity checks on that child chunk.
238    //
239    auto firstChild = (Chunk const*)(firstChildOffset + (Byte const*)this);
240    return BoundsCheckedChunkPtr(firstChild, availableSizeForChildren);
241}
242
243DataChunk const* ListChunk::findDataChunk(Chunk::Type type) const
244{
245    for (auto chunk : getChildren())
246    {
247        auto dataChunk = as<DataChunk>(chunk);
248        if (!dataChunk)
249            continue;
250
251        if (dataChunk->getType() != type)
252            continue;
253
254        return dataChunk;
255    }
256    return nullptr;
257}
258
259ListChunk const* ListChunk::findListChunk(Chunk::Type type) const
260{
261    for (auto chunk : getChildren())
262    {
263        auto listChunk = as<ListChunk>(chunk);
264        if (!listChunk)
265            continue;
266
267        if (listChunk->getType() != type)
268            continue;
269
270        return listChunk;
271    }
272    return nullptr;
273}
274
275ListChunk const* ListChunk::findListChunkRec(Chunk::Type type) const
276{
277    // Note: The search being performed here could
278    // be implemented without any need for recursion
279    // (or a stack), by taking advantage of the way
280    // that RIFF chunks are laid out. If we have some
281    // chunk C, then the next aligned offset in memory
282    // after C is either at the end of the hierarchy,
283    // or it is the next sibling of one of C's ancestors
284    // (where C is being counted as its own ancestor).
285    //
286    // However, it's not really clear if there's enough
287    // of a benefit to justify that more subtle implementation.
288
289    if (getType() == type)
290        return this;
291
292    for (auto chunk : getChildren())
293    {
294        auto listChunk = as<ListChunk>(chunk);
295        if (!listChunk)
296            continue;
297
298        auto found = listChunk->findListChunkRec(type);
299        if (!found)
300            continue;
301
302        return found;
303    }
304    return nullptr;
305}
306
307
308//
309// RIFF::RootChunk
310//
311
312RootChunk const* RootChunk::getFromBlob(void const* data, size_t dataSize)
313{
314    // Our goal is to determine whether the given
315    // blob superficially looks like a RIFF.
316
317    // The data pointer should be non-null if there
318    // was any data passed in.
319    //
320    SLANG_ASSERT(data || !dataSize);
321
322    // If there's no data, then it's obvious not usable.
323    //
324    if (!data)
325        return nullptr;
326
327    // If there isn't even enough data to store the header
328    // for a root, chunk, then the blob is too small.
329    //
330    if (dataSize < sizeof(RootChunk::Header))
331        return nullptr;
332
333    // We cast the data pointer to a root chunk here, so that
334    // we can access the fields in the header, but we are not
335    // yet convinced it is actually a valid RIFF, so we may
336    // still return `nullptr`.
337    //
338    auto rootChunk = reinterpret_cast<RootChunk const*>(data);
339
340    // The root chunk of a valid RIFF should have the `"RIFF"`
341    // tag. This acts as a kind of "magic number" to mark the
342    // start of a RIFF.
343    //
344    if (rootChunk->getTag() != RootChunk::kTag)
345        return nullptr;
346
347    // By reading the size field from the root chunk, we can
348    // determine how big of a file the root chunk claims that
349    // we have.
350    //
351    auto reportedSize = rootChunk->getTotalSize();
352
353    // If the size implied by the RIFF header is larger than the
354    // blob, then we do not have a properly structured RIFF,
355    // and we would be at risk of reading past the end of the
356    // buffer if we attempted to use it.
357    //
358    if (reportedSize > dataSize)
359        return nullptr;
360
361    // Note: It is possible that the `reportedSize` is strictly
362    // *less than* the `dataSize` that was passed in, and there
363    // is a policy choice to be made about how to handle that case.
364    //
365    // We err on the side of leniency here, because the client who
366    // is calling this function might intentionally be storing
367    // additional data in the same blob, after the RIFF, and could
368    // use the RIFF's ability to report its own size as a way to
369    // locate that data.
370
371    // Note: At this point we could recursively walk the hierarchy
372    // of the RIFF and validate that all the contained chunks appear
373    // valid in terms of the sizes they report, but doing so would
374    // take an amount of time that scales with the size of the RIFF,
375    // and our goal here is to be efficient.
376    //
377    // Access to the data through the `RIFF::Chunk` API will do its
378    // best to validate the information in chunks as they are
379    // accessed. The code is attempting to be able to catch
380    // corrupted or accidentally malformed input, but is not aspiring
381    // to anything like proper security.
382
383    return rootChunk;
384}
385
386RootChunk const* RootChunk::getFromBlob(ISlangBlob* blob)
387{
388    SLANG_ASSERT(blob);
389    return getFromBlob(blob->getBufferPointer(), blob->getBufferSize());
390}
391
392//
393// RIFF::ChunkBuilder
394//
395
396Size ChunkBuilder::_updateCachedTotalSize() const
397{
398    Size totalSize = 0;
399    if (auto dataChunk = as<DataChunkBuilder>(this))
400    {
401        // Every chunk starts with a header.
402        //
403        totalSize += sizeof(DataChunk::Header);
404
405        // After the header comes the payload of
406        // the chunk, which for a data chunk
407        // will be the concatenation of all its
408        // shards.
409        //
410        for (auto shard : dataChunk->getShards())
411        {
412            totalSize += shard->getPayloadSize();
413        }
414    }
415    else if (auto listChunk = as<ListChunkBuilder>(this))
416    {
417        // A list chunk starts with a header, just
418        // like a data chunk, although its header
419        // is larger.
420        //
421        totalSize += sizeof(ListChunk::Header);
422
423        // After the header come the child chunks, in order.
424        //
425        for (auto child : listChunk->getChildren())
426        {
427            // We recursively poke the child chunks to
428            // update their cached total size, so that
429            // we can be sure we are getting correct
430            // information.
431            //
432            auto childSize = child->_updateCachedTotalSize();
433
434            // We cannot simply add the size of the child
435            // chunk directly to `totalSize`, because a
436            // RIFF guarantees that every chunk must start
437            // on a suitably aligned boundary. Thus, we
438            // first round `totalSize` up to the necessary
439            // alignment, and then add the child's size.
440            //
441            totalSize = _roundUpToChunkAlignment(totalSize);
442            totalSize += childSize;
443        }
444    }
445    else
446    {
447        SLANG_UNREACHABLE("RIFF chunk must be data or list");
448    }
449
450    _cachedTotalSize = totalSize;
451    return totalSize;
452}
453
454Result ChunkBuilder::_writeTo(Stream* stream) const
455{
456    // The size information that gets written into
457    // the chunk header will be based on the cached
458    // size information for this chunk.
459    //
460    // If nobody has called `_updateCachedTotalSize()`
461    // at all, then that is a problem.
462    //
463    SLANG_ASSERT(_getCachedTotalSize() >= sizeof(Chunk::Header));
464
465    // The size that gets written into the chunk header
466    // is the total size of the chunk, ecluding the chunk
467    // header. Note that this size will *include* the
468    // additional field of the list chunk header.
469    //
470    Size totalSizeExcludingChunkHeader = _getCachedTotalSize() - sizeof(Chunk::Header);
471
472    // Because the size field in the header is only 32 bits,
473    // we want to double-check that it can actually represent
474    // the size of the data to be written into it.
475    //
476    UInt32 sizeToWriteInHeader = UInt32(totalSizeExcludingChunkHeader);
477    SLANG_ASSERT(Size(sizeToWriteInHeader) == totalSizeExcludingChunkHeader);
478
479    if (auto dataChunk = as<DataChunkBuilder>(this))
480    {
481        // We start by writing the header.
482        //
483        DataChunk::Header header;
484        header.size = sizeToWriteInHeader;
485
486        // The tag of a data chunk is its type `FourCC`.
487        //
488        header.tag = dataChunk->getType();
489
490        // Once we've filled in the header fields, we
491        // can write it to the output stream.
492        //
493        SLANG_RETURN_ON_FAIL(stream->write(&header, sizeof(header)));
494
495        // Now we can simply write the payload bytes,
496        // which are the concatenation of the payloads
497        // of all the shards.
498        //
499        for (auto shard : dataChunk->getShards())
500        {
501            auto payload = shard->getPayload();
502            auto payloadSize = shard->getPayloadSize();
503            SLANG_RETURN_ON_FAIL(stream->write(payload, payloadSize));
504        }
505    }
506    else if (auto listChunk = as<ListChunkBuilder>(this))
507    {
508        // We start by writing the header.
509        //
510        ListChunk::Header header;
511        header.chunkHeader.size = sizeToWriteInHeader;
512
513        // The tag of a list chunk is either `"RIFF"`
514        // (for a root chunk) or `"LIST"` (for any
515        // other list chunk).
516        //
517        header.chunkHeader.tag =
518            listChunk->getKind() == Chunk::Kind::Root ? RootChunk::kTag : ListChunk::kTag;
519
520        // The type of a list chunk is stored in the
521        // additional header field after the base
522        // chunk header.
523        //
524        header.type = listChunk->getType();
525
526        // Once we've filled in the header fields, we
527        // can write it to the output stream.
528        //
529        SLANG_RETURN_ON_FAIL(stream->write(&header, sizeof(header)));
530
531        // Now we recursively write each of the child chunks,
532        // keeping track of the total size so far, so that
533        // we can insert padding as needing to bring things
534        // up to alignment.
535        //
536        Size totalSize = sizeof(header);
537        for (auto child : listChunk->getChildren())
538        {
539            // We note the total size written so far,
540            // as well as the size after rounding up
541            // to the required alignment.
542            //
543            auto unalignedSize = totalSize;
544            auto alignedSize = _roundUpToChunkAlignment(unalignedSize);
545            SLANG_ASSERT(alignedSize >= unalignedSize);
546
547            // If the aligned size is greater than the
548            // unaligned size, then we may need to write
549            // some padding bytes into the output stream.
550            //
551            auto paddingSize = alignedSize - unalignedSize;
552
553            // The amount of padding to be inserted must
554            // always be less than the minimum chunk alignment.
555            //
556            SLANG_ASSERT(paddingSize < Chunk::kChunkAlignment);
557
558            // We'll write padding bytes to get things up
559            // to the necessary alignment.
560            //
561            auto remainingPaddingToWrite = paddingSize;
562            while (remainingPaddingToWrite--)
563            {
564                static const Byte kPadding[1] = {0};
565                stream->write(kPadding, 1);
566            }
567
568            // Now we are at a suitably aligned offset.
569            //
570            totalSize = alignedSize;
571
572            // With the alignment concern dealt with,
573            // we can simply recursively write the
574            // child chunk and update the total size.
575            //
576            SLANG_RETURN_ON_FAIL(child->_writeTo(stream));
577            totalSize += child->_getCachedTotalSize();
578        }
579
580        // As a validation check, we expect the total number
581        // of bytes we've written here to match the total
582        // size that was cached on this chunk (and that was
583        // written into the chunk header).
584        //
585        SLANG_ASSERT(totalSize == _getCachedTotalSize());
586    }
587    else
588    {
589        SLANG_UNREACHABLE("RIFF chunk must be data or list");
590    }
591    return SLANG_OK;
592}
593
594MemoryArena& ChunkBuilder::_getMemoryArena() const
595{
596    return getRIFFBuilder()->_getMemoryArena();
597}
598
599//
600// RIFF::ListChunkBuilder
601//
602
603DataChunkBuilder* ListChunkBuilder::addDataChunk(Chunk::Type type)
604{
605    auto chunk = new (_getMemoryArena()) DataChunkBuilder(type, this);
606    _children.add(chunk);
607    return chunk;
608}
609
610ListChunkBuilder* ListChunkBuilder::addListChunk(Chunk::Type type)
611{
612    auto chunk = new (_getMemoryArena()) ListChunkBuilder(type, this);
613    _children.add(chunk);
614    return chunk;
615}
616
617//
618// RIFF::DataChunkBuilder
619//
620
621void DataChunkBuilder::addData(void const* data, Size size)
622{
623    // Adding no data should be a no-op.
624    //
625    if (size == 0)
626        return;
627
628    // The most interesting implementation detail here
629    // is that we will try to detect cases where we
630    // can re-use an existing `Shard` by adding the data
631    // to the end of that shard's allocation.
632    //
633    // This is only possible because of the way that
634    // we are using a single `MemoryArena` to allocate
635    // everything, which makes it possible that the
636    // next address the arena would return for an allocation
637    // of `size` bytes is the same as the ending address
638    // of the payload for the last shard of this chunk.
639    //
640    auto& arena = _getMemoryArena();
641
642    // We start by checking if this chunk already has
643    // a last shard that we could consider appending to.
644    //
645    auto lastShard = _shards.getLast();
646    if (lastShard)
647    {
648        // If there is a last shard, then we can compute
649        // the end address of its payload, and see if
650        // it is the same as the cursor of the arena
651        // we are allocating from.
652        //
653        auto payload = lastShard->getPayload();
654        auto payloadSize = lastShard->getPayloadSize();
655        auto payloadEnd = (Byte*)payload + payloadSize;
656        if (payloadEnd == arena.getCursor())
657        {
658            // Now that we've confirmed that the shard's
659            // payload ends at an address the arena could
660            // conceivably allocate from, we need to ask
661            // the arena to allocate `size` bytes from
662            // the current block it is using, and see if
663            // doing so succeeds.
664            //
665            if (arena.allocateCurrentUnaligned(size))
666            {
667                // At this point, we've confirmed that we
668                // are in our special case, and the relevant
669                // bytes have been allocated from the arena.
670                //
671                // Now we can simply write the new data at
672                // what used to be the end address for the
673                // shard's payload, and adjust its state
674                // to account for the new allocation.
675                //
676                ::memcpy(payloadEnd, data, size);
677                lastShard->setPayload(payload, payloadSize + size);
678                return;
679            }
680        }
681    }
682
683    // If we didn't land in our special case, we
684    // will simply allocate a new shard to hold
685    // the data.
686    //
687    // Note that the order of allocation here is
688    // intentional, and supports the optimized special
689    // case that we checked for above. We make the
690    // allocation for the payload *last*, so that
691    // it is possible that the arena's next allocation
692    // could come right after the payload allocation
693    // in memory.
694    //
695    // If we allocated the payload first and the
696    // `Shard` second, then there would already be
697    // another allocation after the payload, and
698    // the optimized case would never trigger.
699    //
700    auto shard = _addShard();
701    auto payload = arena.allocateUnaligned(size);
702    ::memcpy(payload, data, size);
703    shard->setPayload(payload, size);
704}
705
706void DataChunkBuilder::addUnownedData(void const* data, size_t size)
707{
708    // Unowned data will always have to be added as its own shard.
709    //
710    auto shard = _addShard();
711    shard->setPayload(data, size);
712}
713
714DataChunkBuilder::Shard* DataChunkBuilder::_addShard()
715{
716    auto shard = new (_getMemoryArena()) Shard();
717    _shards.add(shard);
718    return shard;
719}
720
721//
722// RIFF::Builder
723//
724
725Builder::Builder()
726    : _arena(4096)
727{
728}
729
730Result Builder::writeTo(Stream* stream)
731{
732    // If there's no root chunk, then this isn't
733    // a well-formed RIFF.
734    //
735    if (!_rootChunk)
736        return SLANG_FAIL;
737
738    // The `ChunkBuilder::_writeTo()` method requires size
739    // information for each of the chunks in the hierarchy.
740    // Rather than try to keep size information up-to-date
741    // during the building process, we simply compute it
742    // all at once, right before writing the output.
743    //
744    _rootChunk->_updateCachedTotalSize();
745    _rootChunk->_writeTo(stream);
746    return SLANG_OK;
747}
748
749Result Builder::writeToBlob(ISlangBlob** outBlob)
750{
751    OwnedMemoryStream stream(FileAccess::Write);
752    SLANG_RETURN_ON_FAIL(writeTo(&stream));
753
754    List<uint8_t> data;
755    stream.swapContents(data);
756
757    *outBlob = ListBlob::moveCreate(data).detach();
758    return SLANG_OK;
759}
760
761ListChunkBuilder* Builder::addRootChunk(Chunk::Type type)
762{
763    // There must not already be a root chunk set.
764    SLANG_ASSERT(getRootChunk() == nullptr);
765
766    auto chunk = new (_getMemoryArena()) ListChunkBuilder(type, this);
767    _rootChunk = chunk;
768    return chunk;
769}
770
771
772//
773// RIFF::BuildCursor
774//
775
776BuildCursor::BuildCursor() {}
777
778BuildCursor::BuildCursor(Builder& builder)
779    : _riffBuilder(&builder)
780{
781}
782
783BuildCursor::BuildCursor(ChunkBuilder* chunk)
784{
785    setCurrentChunk(chunk);
786}
787
788void BuildCursor::setCurrentChunk(ChunkBuilder* chunk)
789{
790    _currentChunk = chunk;
791    _riffBuilder = chunk ? chunk->getRIFFBuilder() : nullptr;
792}
793
794DataChunkBuilder* BuildCursor::addDataChunk(Chunk::Type type)
795{
796    // The current chunk must be a list chunk, so that
797    // we can add children to it.
798    //
799    auto parentChunk = as<ListChunkBuilder>(getCurrentChunk());
800    SLANG_ASSERT(parentChunk);
801
802    return parentChunk->addDataChunk(type);
803}
804
805void BuildCursor::addDataChunk(Chunk::Type type, void const* data, size_t size)
806{
807    beginDataChunk(type);
808    addData(data, size);
809    endChunk();
810}
811
812ListChunkBuilder* BuildCursor::addListChunk(Chunk::Type type)
813{
814    // If there is no current chunk being written into,
815    // then an attempt to add a new chunk should set
816    // the root chunk of the entire RIFF.
817    //
818    auto currentChunk = getCurrentChunk();
819    if (!currentChunk)
820    {
821        SLANG_ASSERT(getRIFFBuilder());
822        return _riffBuilder->addRootChunk(type);
823    }
824
825    // Otherwise, the current chunk must be a list
826    // chunk, and we add a new child to it.
827    //
828    auto parentChunk = as<ListChunkBuilder>(currentChunk);
829    SLANG_ASSERT(parentChunk);
830
831    return parentChunk->addListChunk(type);
832}
833
834void BuildCursor::beginDataChunk(Chunk::Type type)
835{
836    auto chunk = addDataChunk(type);
837    setCurrentChunk(chunk);
838}
839
840void BuildCursor::beginListChunk(Chunk::Type type)
841{
842    auto chunk = addListChunk(type);
843    setCurrentChunk(chunk);
844}
845
846void BuildCursor::endChunk()
847{
848    SLANG_ASSERT(getCurrentChunk() != nullptr);
849
850    auto chunk = getCurrentChunk();
851    setCurrentChunk(chunk->getParent());
852}
853
854void BuildCursor::addData(void const* data, Size size)
855{
856    // The current chunk must be a data chunk, so that
857    // we can add data to it.
858    //
859    auto dataChunk = as<DataChunkBuilder>(getCurrentChunk());
860    SLANG_ASSERT(dataChunk);
861
862    dataChunk->addData(data, size);
863}
864
865void BuildCursor::addUnownedData(void const* data, Size size)
866{
867    // The current chunk must be a data chunk, so that
868    // we can add data to it.
869    //
870    auto dataChunk = as<DataChunkBuilder>(getCurrentChunk());
871    SLANG_ASSERT(dataChunk);
872
873    dataChunk->addUnownedData(data, size);
874}
875
876} // namespace RIFF
877
878} // namespace Slang