yum-mirror/slang

Making it easier to work with shaders

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

Theresa FoleyAdd a memory-mappable binary serialization format (#7222)ec7ab914f

master
12.7 KiB473 linesraw
1// slang-blob-builder.cpp
2#include "slang-blob-builder.h"
3
4namespace Slang
5{
6
7//
8// BlobBuilder
9//
10
11BlobBuilder::BlobBuilder()
12    : _arena(4096)
13{
14}
15
16void BlobBuilder::writeTo(Stream* stream)
17{
18    // First we scan through all the chunks to set their
19    // absolute offsets, which enables us to compute the
20    // correct values for relative pointers when we write
21    // them out.
22    //
23    SLANG_MAYBE_UNUSED
24    Size sizeComputed = _calcSizeAndSetCachedChunkOffsets();
25
26    // Now we can scan through the chunks again and write
27    // the bytes of each of their shards.
28    //
29    SLANG_MAYBE_UNUSED
30    Size sizeWritten = _writeChunksTo(stream);
31
32    SLANG_ASSERT(sizeComputed == sizeWritten);
33}
34
35Size BlobBuilder::_calcSizeAndSetCachedChunkOffsets()
36{
37    Size totalSize = 0;
38    for (auto chunk : _chunks)
39    {
40        auto chunkPrefixSize = chunk->getPrefixSize();
41        auto chunkContentSize = chunk->getContentSize();
42        auto chunkAlignment = chunk->getAlignment();
43
44        // We add the size of the chunk prefix (if any) *before*
45        // aligning the current offset. Doing it this way
46        // means that sometimes the prefix can fit "for free"
47        // in space that would otherwise be padding.
48        //
49        // For example, if the current `totalSize` is 4, the
50        // `chunkAlignment` is 16, and the `chunkPrefixSize` is 8,
51        // then the sequence will be:
52        //
53        // * Add `totalSize += chunkPrefixSize`, resulting in a `totalSize`
54        //   of 12.
55        //
56        // * Round the `totalSize` up to the `chunkAlignment`, to compute
57        //   a `chunkOffset` of 16.
58        //
59        // The result is that the chunk's content starts at offset 16,
60        // and the prefix can occupy the 8 bytes before that (so the
61        // prefix is at offset 8).
62        //
63        // In the best case this approach can save a few bytes here or
64        // there when `chunkAlignment` is larger than the `chunkPrefixSize`,
65        // and in the worst case it does no harm.
66
67        totalSize += chunkPrefixSize;
68        auto chunkOffset = roundUpToAlignment(totalSize, chunkAlignment);
69
70        chunk->_setCachedOffset(chunkOffset);
71
72        totalSize = chunkOffset + chunkContentSize;
73    }
74    return totalSize;
75}
76
77Size BlobBuilder::_writeChunksTo(Stream* stream)
78{
79    Size totalSize = 0;
80    for (auto chunk : _chunks)
81    {
82        auto chunkPrefixSize = chunk->getPrefixSize();
83        auto chunkContentSize = chunk->getContentSize();
84        auto chunkOffset = chunk->_getCachedOffset();
85
86        SLANG_ASSERT(
87            chunkOffset == roundUpToAlignment(totalSize + chunkPrefixSize, chunk->getAlignment()));
88
89        auto paddingSize = chunkOffset - totalSize;
90
91        // The "padding" before the chunk's content also
92        // includes the space reserved for the chunk's prefix.
93        //
94        // We can thus subtract the prefix size from the number
95        // of pad bytes to write.
96
97        SLANG_ASSERT(paddingSize >= chunkPrefixSize);
98        paddingSize -= chunkPrefixSize;
99
100        while (paddingSize--)
101        {
102            Byte padding = 0;
103            stream->write(&padding, sizeof(padding));
104        }
105
106        // The `ChunkBuilder::_writeTo()` call will write the
107        // prefix *and* the chunk content, so it is appropriate
108        // to call it here even when the total number of bytes
109        // written to the stream is not equal to the `chunkOffset`
110        // (because in that case the total bytes written so far
111        // should be `chunkOffset - chunkPrefixSize`).
112        //
113        chunk->_writeTo(stream);
114
115        totalSize = chunkOffset + chunkContentSize;
116    }
117
118    return totalSize;
119}
120
121void BlobBuilder::writeToBlob(ISlangBlob** outBlob)
122{
123    OwnedMemoryStream stream(FileAccess::Write);
124    writeTo(&stream);
125
126    List<uint8_t> data;
127    stream.swapContents(data);
128
129    *outBlob = ListBlob::moveCreate(data).detach();
130}
131
132ChunkBuilder* BlobBuilder::createUnparentedChunk()
133{
134    auto chunk = new (_arena) ChunkBuilder(this);
135    return chunk;
136}
137
138void BlobBuilder::addChunk(ChunkBuilder* chunk)
139{
140    // TODO(tfoley): it would be good to have a way to assert
141    // that the chunk has not already been added.
142
143    _chunks.add(chunk);
144}
145
146ChunkBuilder* BlobBuilder::addChunk()
147{
148    auto chunk = new (_arena) ChunkBuilder(this);
149    _chunks.add(chunk);
150    return chunk;
151}
152
153ChunkBuilder* BlobBuilder::addChunkAfter(ChunkBuilder* existingChunk)
154{
155    auto newChunk = new (_arena) ChunkBuilder(this);
156    _chunks.insertAfter(existingChunk, newChunk);
157    return newChunk;
158}
159
160//
161// ChunkBuilder
162//
163
164void ChunkBuilder::setAlignmentToAtLeast(Size alignment)
165{
166    SLANG_ASSERT(alignment > 0);
167    SLANG_ASSERT(isPowerOfTwo(alignment));
168
169    _contentAlignment = std::max(_contentAlignment, alignment);
170}
171
172void ChunkBuilder::writePaddingToAlignTo(Size alignment)
173{
174    setAlignmentToAtLeast(alignment);
175
176    auto alignedSize = roundUpToAlignment(_contentSize, alignment);
177
178    auto requiredPaddingSize = alignedSize - _contentSize;
179
180    while (requiredPaddingSize)
181    {
182        Byte padByte = 0;
183        writeData(&padByte, sizeof(padByte));
184        requiredPaddingSize -= sizeof(padByte);
185    }
186}
187
188ChunkBuilder::ChunkBuilder(BlobBuilder* parentBlob)
189    : _parentBlob(parentBlob)
190{
191}
192
193Size ChunkBuilder::getContentSize() const
194{
195    return _contentSize;
196}
197
198Size ChunkBuilder::getPrefixSize() const
199{
200    if (!_prefixShard)
201        return 0;
202
203    return _prefixShard->getSize();
204}
205
206Size ChunkBuilder::getAlignment() const
207{
208    return _contentAlignment;
209}
210
211
212void ChunkBuilder::writeData(void const* data, Size size)
213{
214    // Adding no data should be a no-op.
215    //
216    if (size == 0)
217        return;
218
219
220    // The most interesting implementation detail here
221    // is that we will try to detect cases where we
222    // can re-use an existing `ShardBuilder` by adding the data
223    // to the end of that shard's allocation.
224    //
225    // This is only possible because of the way that
226    // we are using a single `MemoryArena` to allocate
227    // everything, which makes it possible that the
228    // next address the arena would return for an allocation
229    // of `size` bytes is the same as the ending address
230    // of the payload for the last shard of this chunk.
231    //
232    auto& arena = getParentBlob()->_getArena();
233
234    // We start by checking if this chunk already has
235    // a last shard that we could consider appending to.
236    //
237    auto lastShard = _childShards.getLast();
238    if (lastShard && lastShard->_kind == ShardBuilder::Kind::Data)
239    {
240        // If there is a last shard, then we can compute
241        // the end address of its payload, and see if
242        // it is the same as the cursor of the arena
243        // we are allocating from.
244        //
245        auto payload = lastShard->_data.ptr;
246        auto payloadSize = lastShard->_size;
247        auto payloadEnd = (Byte*)payload + payloadSize;
248        if (payloadEnd == arena.getCursor())
249        {
250            // Now that we've confirmed that the shard's
251            // payload ends at an address the arena could
252            // conceivably allocate from, we need to ask
253            // the arena to allocate `size` bytes from
254            // the current block it is using, and see if
255            // doing so succeeds.
256            //
257            if (arena.allocateCurrentUnaligned(size))
258            {
259                // At this point, we've confirmed that we
260                // are in our special case, and the relevant
261                // bytes have been allocated from the arena.
262                //
263                // Now we can simply write the new data at
264                // what used to be the end address for the
265                // shard's payload, and adjust its state
266                // to account for the new allocation.
267                //
268                ::memcpy(payloadEnd, data, size);
269
270                lastShard->_size = payloadSize + size;
271                _contentSize += size;
272                return;
273            }
274        }
275    }
276
277    // If the special case above doesn't apply, we simply
278    // allocate a new shard to hold the data that was
279    // passed in.
280    //
281    auto shard = _createDataShard(data, size);
282    _childShards.add(shard);
283    _contentSize += size;
284}
285
286void ChunkBuilder::addContentsOf(ChunkBuilder* otherChunk)
287{
288    auto otherPrefixShard = otherChunk->_prefixShard;
289    auto otherChunkSize = otherChunk->getContentSize();
290    auto otherChunkAlignment = otherChunk->getAlignment();
291    auto otherChunkShards = otherChunk->_childShards;
292
293    otherChunk->_prefixShard = nullptr;
294    otherChunk->_contentSize = 0;
295    otherChunk->_contentAlignment = 1;
296    otherChunk->_childShards = InternallyLinkedList<ShardBuilder>();
297
298    if (otherPrefixShard)
299    {
300        // If the other chunk included a prefix, then
301        // it only makes sense to append it in the case
302        // where *this* chunk is completely empty.
303
304        SLANG_ASSERT(!_prefixShard);
305        SLANG_ASSERT(!_childShards.getFirst());
306        _prefixShard = otherPrefixShard;
307    }
308
309    writePaddingToAlignTo(otherChunkAlignment);
310    _childShards.append(otherChunkShards);
311    _contentSize += otherChunkSize;
312}
313
314ChunkBuilder* ChunkBuilder::addChunkAfter()
315{
316    return getParentBlob()->addChunkAfter(this);
317}
318
319void ChunkBuilder::_writeRelativePtr(ChunkBuilder* targetChunk, Size ptrSize)
320{
321    SLANG_ASSERT(ptrSize != 0);
322    SLANG_ASSERT(ptrSize <= sizeof(UInt64));
323
324    writePaddingToAlignTo(ptrSize);
325
326    if (!targetChunk)
327    {
328        UInt64 value = 0;
329        writeData(&value, ptrSize);
330        return;
331    }
332
333    auto shard = _createRelativePtrShard(targetChunk, ptrSize);
334    _childShards.add(shard);
335    _contentSize += ptrSize;
336}
337
338void ChunkBuilder::_addPrefixRelativePtr(ChunkBuilder* targetChunk, Size ptrSize)
339{
340    SLANG_ASSERT(targetChunk != nullptr);
341    SLANG_ASSERT(ptrSize != 0);
342
343    SLANG_ASSERT(!_prefixShard);
344
345    setAlignmentToAtLeast(ptrSize);
346
347    auto shard = _createRelativePtrShard(targetChunk, ptrSize);
348    _prefixShard = shard;
349}
350
351void ChunkBuilder::addPrefixData(void const* data, Size size)
352{
353    SLANG_ASSERT(data != nullptr);
354    SLANG_ASSERT(size != 0);
355
356    SLANG_ASSERT(!_prefixShard);
357
358    setAlignmentToAtLeast(size);
359
360    auto shard = _createDataShard(data, size);
361    _prefixShard = shard;
362}
363
364ShardBuilder* ChunkBuilder::_createDataShard(void const* data, Size size)
365{
366    auto& arena = getParentBlob()->_getArena();
367    auto shard = new (arena) ShardBuilder(ShardBuilder::Kind::Data);
368
369    auto shardData = arena.allocateUnaligned(size);
370    ::memcpy(shardData, data, size);
371
372    shard->_data.ptr = shardData;
373    shard->_size = size;
374
375    return shard;
376}
377
378ShardBuilder* ChunkBuilder::_createRelativePtrShard(ChunkBuilder* targetChunk, Size ptrSize)
379{
380    auto& arena = getParentBlob()->_getArena();
381    auto shard = new (arena) ShardBuilder(ShardBuilder::Kind::RelativePtr);
382
383    shard->_relativePtr.targetChunk = targetChunk;
384    shard->_size = ptrSize;
385
386    return shard;
387}
388
389void ChunkBuilder::_writeTo(Stream* stream)
390{
391    auto chunkOffset = _getCachedOffset();
392
393    if (_prefixShard)
394    {
395        // Note that the prefix is written *before* the
396        // starting offset of the chunk's content, so we
397        // compute the appropriate offset to pass down.
398        //
399        auto prefixSize = _prefixShard->getSize();
400        auto prefixOffset = chunkOffset - prefixSize;
401
402        _prefixShard->_writeTo(stream, prefixOffset);
403    }
404
405    auto shardOffset = chunkOffset;
406    for (auto shard : _childShards)
407    {
408        shard->_writeTo(stream, shardOffset);
409        shardOffset += shard->getSize();
410    }
411    SLANG_ASSERT(shardOffset == chunkOffset + getContentSize());
412}
413
414
415//
416// ShardBuilder
417//
418
419ShardBuilder::ShardBuilder(Kind kind)
420    : _kind(kind)
421{
422}
423
424void ShardBuilder::_writeTo(Stream* stream, Size inSelfOffset)
425{
426    switch (_kind)
427    {
428    case Kind::Data:
429        {
430            stream->write(_data.ptr, _size);
431        }
432        break;
433
434    case Kind::RelativePtr:
435        {
436            auto targetChunk = _relativePtr.targetChunk;
437            SLANG_ASSERT(targetChunk);
438
439            auto selfOffset = intptr_t(inSelfOffset);
440            auto targetOffset = intptr_t(targetChunk->_getCachedOffset());
441
442            intptr_t relativeOffset = targetOffset - selfOffset;
443
444            switch (_size)
445            {
446            case sizeof(Int32):
447                {
448                    auto value = Int32(relativeOffset);
449                    stream->write(&value, sizeof(value));
450                }
451                break;
452
453            case sizeof(Int64):
454                {
455                    auto value = Int64(relativeOffset);
456                    stream->write(&value, sizeof(value));
457                }
458                break;
459
460            default:
461                SLANG_UNEXPECTED("unsupported relative pointer size");
462                break;
463            }
464        }
465        break;
466
467    default:
468        SLANG_UNEXPECTED("unknown Fossil::ShardBuilder::Kind");
469        break;
470    }
471}
472
473} // namespace Slang